diff --git a/README.md b/README.md index b308cea..fda4031 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ We have deployed an example bound to a free Suno account, so it has daily usage ## Features - Perfectly implements the creation API from app.suno.ai +- Automatically keep the account active. - Supports Custom Mode - One-click deployment to Vercel - In addition to the standard API, it also adapts to the API Schema of Agent platforms like GPTs and Coze, so you can use it as a tool/plugin/Action for LLMs and integrate it into any AI Agent. @@ -106,14 +107,16 @@ Suno API currently mainly implements the following APIs: ```bash - `/api/generate`: Generate music - `/api/custom_generate`: Generate music (Custom Mode, support setting lyrics, music style, title, etc.) -- `/api/get`: Get music Info +- `/api/generate_lyrics`: Generate lyrics based on prompt +- `/api/get`: Get music information based on the id. Use “,” to separate multiple ids. + If no IDs are provided, all music will be returned. - `/api/get_limit`: Get quota Info ``` For more detailed documentation, please check out the demo site: [suno.gcui.art/docs](https://suno.gcui.art/docs) -## Code example +## API Integration Code Example ### Python @@ -173,59 +176,63 @@ if __name__ == '__main__': ### Js ```js -const axios = require('axios'); +const axios = require("axios"); // replace your vercel domain -const baseUrl = 'http://localhost:3000'; +const baseUrl = "http://localhost:3000"; async function customGenerateAudio(payload) { - const url = `${baseUrl}/api/custom_generate`; - const response = await axios.post(url, payload, { headers: { 'Content-Type': 'application/json' } }); - return response.data; + const url = `${baseUrl}/api/custom_generate`; + const response = await axios.post(url, payload, { + headers: { "Content-Type": "application/json" }, + }); + return response.data; } async function generateAudioByPrompt(payload) { - const url = `${baseUrl}/api/generate`; - const response = await axios.post(url, payload, { headers: { 'Content-Type': 'application/json' } }); - return response.data; + const url = `${baseUrl}/api/generate`; + const response = await axios.post(url, payload, { + headers: { "Content-Type": "application/json" }, + }); + return response.data; } async function getAudioInformation(audioIds) { - const url = `${baseUrl}/api/get?ids=${audioIds}`; - const response = await axios.get(url); - return response.data; + const url = `${baseUrl}/api/get?ids=${audioIds}`; + const response = await axios.get(url); + return response.data; } async function getQuotaInformation() { - const url = `${baseUrl}/api/get_limit`; - const response = await axios.get(url); - return response.data; + const url = `${baseUrl}/api/get_limit`; + const response = await axios.get(url); + return response.data; } async function main() { - const data = await generateAudioByPrompt({ - prompt: "A popular heavy metal song about war, sung by a deep-voiced male singer, slowly and melodiously. The lyrics depict the sorrow of people after the war.", - make_instrumental: false, - wait_audio: false - }); + const data = await generateAudioByPrompt({ + prompt: + "A popular heavy metal song about war, sung by a deep-voiced male singer, slowly and melodiously. The lyrics depict the sorrow of people after the war.", + make_instrumental: false, + wait_audio: false, + }); - const ids = `${data[0].id},${data[1].id}`; - console.log(`ids: ${ids}`); + const ids = `${data[0].id},${data[1].id}`; + console.log(`ids: ${ids}`); - for (let i = 0; i < 60; i++) { - const data = await getAudioInformation(ids); - if (data[0].status === 'streaming') { - console.log(`${data[0].id} ==> ${data[0].audio_url}`); - console.log(`${data[1].id} ==> ${data[1].audio_url}`); - break; - } - // sleep 5s - await new Promise(resolve => setTimeout(resolve, 5000)); + for (let i = 0; i < 60; i++) { + const data = await getAudioInformation(ids); + if (data[0].status === "streaming") { + console.log(`${data[0].id} ==> ${data[0].audio_url}`); + console.log(`${data[1].id} ==> ${data[1].audio_url}`); + break; } + // sleep 5s + await new Promise((resolve) => setTimeout(resolve, 5000)); + } } main(); - ``` ## Integration with Custom Agents diff --git a/README_CN.md b/README_CN.md index 6a4a56d..df46295 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,7 +4,7 @@ [English](./README.md) | [简体中文](./README_CN.md) -用 API 调用 suno.ai 的音乐生成AI,并且可以轻松集成到 GPTs 等 agent 中。 +用 API 调用 suno.ai 的音乐生成 AI,并且可以轻松集成到 GPTs 等 agent 中。 ## 简介 @@ -20,7 +20,8 @@ Suno.ai v3 是一个令人惊叹的 AI 音乐服务,虽然官方还没有开 ## Features -- 完美的实现了 app.suno.ai 中的创作 API +- 完美的实现了 app.suno.ai 中的大部分 API +- 自动保持账号活跃 - 支持 Custom Mode - 一键部署到 vercel - 除了标准 API,还适配了 GPTs、coze 等 Agent 平台的 API Schema,所以你可以把它当做一个 LLM 的工具/插件/Action,集成到任意 AI Agent 中。 @@ -103,14 +104,15 @@ Suno API 目前主要实现了以下 API: ```bash - `/api/generate`: 创建音乐 - `/api/custom_generate`: 创建音乐(自定义模式,支持设置歌词、音乐风格、设置标题等) -- `/api/get`: 获取音乐 +- `/api/generate_lyrics`: 根据Prompt创建歌词 +- `/api/get`: 根据id获取音乐信息。获取多个请用","分隔,不传ids则返回所有音乐 - `/api/get_limit`: 获取配额信息 ``` 详细文档请查看演示站点: [suno.gcui.art/docs](https://suno.gcui.art/docs) -## 代码示例 +## API 集成代码示例 ### Python @@ -170,62 +172,65 @@ if __name__ == '__main__': ### Js ```js -const axios = require('axios'); +const axios = require("axios"); // replace your vercel domain -const baseUrl = 'http://localhost:3000'; +const baseUrl = "http://localhost:3000"; async function customGenerateAudio(payload) { - const url = `${baseUrl}/api/custom_generate`; - const response = await axios.post(url, payload, { headers: { 'Content-Type': 'application/json' } }); - return response.data; + const url = `${baseUrl}/api/custom_generate`; + const response = await axios.post(url, payload, { + headers: { "Content-Type": "application/json" }, + }); + return response.data; } async function generateAudioByPrompt(payload) { - const url = `${baseUrl}/api/generate`; - const response = await axios.post(url, payload, { headers: { 'Content-Type': 'application/json' } }); - return response.data; + const url = `${baseUrl}/api/generate`; + const response = await axios.post(url, payload, { + headers: { "Content-Type": "application/json" }, + }); + return response.data; } async function getAudioInformation(audioIds) { - const url = `${baseUrl}/api/get?ids=${audioIds}`; - const response = await axios.get(url); - return response.data; + const url = `${baseUrl}/api/get?ids=${audioIds}`; + const response = await axios.get(url); + return response.data; } async function getQuotaInformation() { - const url = `${baseUrl}/api/get_limit`; - const response = await axios.get(url); - return response.data; + const url = `${baseUrl}/api/get_limit`; + const response = await axios.get(url); + return response.data; } async function main() { - const data = await generateAudioByPrompt({ - prompt: "A popular heavy metal song about war, sung by a deep-voiced male singer, slowly and melodiously. The lyrics depict the sorrow of people after the war.", - make_instrumental: false, - wait_audio: false - }); + const data = await generateAudioByPrompt({ + prompt: + "A popular heavy metal song about war, sung by a deep-voiced male singer, slowly and melodiously. The lyrics depict the sorrow of people after the war.", + make_instrumental: false, + wait_audio: false, + }); - const ids = `${data[0].id},${data[1].id}`; - console.log(`ids: ${ids}`); + const ids = `${data[0].id},${data[1].id}`; + console.log(`ids: ${ids}`); - for (let i = 0; i < 60; i++) { - const data = await getAudioInformation(ids); - if (data[0].status === 'streaming') { - console.log(`${data[0].id} ==> ${data[0].audio_url}`); - console.log(`${data[1].id} ==> ${data[1].audio_url}`); - break; - } - // sleep 5s - await new Promise(resolve => setTimeout(resolve, 5000)); + for (let i = 0; i < 60; i++) { + const data = await getAudioInformation(ids); + if (data[0].status === "streaming") { + console.log(`${data[0].id} ==> ${data[0].audio_url}`); + console.log(`${data[1].id} ==> ${data[1].audio_url}`); + break; } + // sleep 5s + await new Promise((resolve) => setTimeout(resolve, 5000)); + } } main(); - ``` - ## 集成到到常见的自定义 Agent 中 你可以把 suno ai 当做一个 工具/插件/Action 集成到你的 AI Agent 中。 diff --git a/package.json b/package.json index 985f5ff..f644764 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/gcui-art/" }, "license": "LGPL-3.0-or-later", - "version": "1.0.0", + "version": "1.1.0", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/api/generate_lyrics/route.ts b/src/app/api/generate_lyrics/route.ts new file mode 100644 index 0000000..732ae6b --- /dev/null +++ b/src/app/api/generate_lyrics/route.ts @@ -0,0 +1,42 @@ +import { NextResponse, NextRequest } from "next/server"; +import { sunoApi } from "@/lib/SunoApi"; +export const dynamic = "force-dynamic"; +export async function POST(req: NextRequest) { + if (req.method === 'POST') { + try { + const body = await req.json(); + const { prompt } = body; + + if (!prompt) { + return new NextResponse(JSON.stringify({ error: 'Prompt is required' }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } + + const lyrics = await (await sunoApi).generateLyrics(prompt); + + return new NextResponse(JSON.stringify(lyrics), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + } catch (error: any) { + console.error('Error generating lyrics:', JSON.stringify(error.response.data)); + if (error.response.status === 402) { + return new NextResponse(JSON.stringify({ error: error.response.data.detail }), { + status: 402, + headers: { 'Content-Type': 'application/json' } + }); + } + return new NextResponse(JSON.stringify({ error: 'Internal server error: ' + JSON.stringify(error.response.data.detail) }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + } else { + return new NextResponse('Method Not Allowed', { + headers: { Allow: 'POST' }, + status: 405 + }); + } +} \ No newline at end of file diff --git a/src/app/api/get_limit/route.ts b/src/app/api/get_limit/route.ts index 8094694..9568a44 100644 --- a/src/app/api/get_limit/route.ts +++ b/src/app/api/get_limit/route.ts @@ -15,7 +15,7 @@ export async function GET(req: NextRequest) { } catch (error) { console.error('Error fetching limit:', error); - return new NextResponse(JSON.stringify({ error: 'Internal server error' }), { + return new NextResponse(JSON.stringify({ error: 'Internal server error. ' + error }), { status: 500, headers: { 'Content-Type': 'application/json' } }); diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index af54760..a0af96c 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -16,26 +16,33 @@ export default function Docs() { {` --- -Suno API currently mainly implements the following APIs: +\`gcui-art/suno-api\` currently mainly implements the following APIs: \`\`\`bash - \`/api/generate\`: Generate music - \`/api/custom_generate\`: Generate music (Custom Mode, support setting lyrics, - music style, title, etc.) -- \`/api/get\`: Get music Info + music style, title, etc.) +- \`/api/generate_lyrics\`: Generate lyrics based on prompt +- \`/api/get\`: Get music information based on the id. Use “,” to separate multiple + ids. If no IDs are provided, all music will be returned. - \`/api/get_limit\`: Get quota Info \`\`\` -Feel free to explore the detailed API parameters and conduct tests on this page. -> Please note: -> -> we have bound a free account with a daily usage limit. -> You can deploy and bind your own account to complete the testing. +Feel free to explore the detailed API parameters and conduct tests on this page. `}
+
+

+ Details of the API and testing it online +

+

+ This is just a demo, bound to a test account. Please do not use it frequently, so that more people can test online. +

+
+
diff --git a/src/app/docs/swagger-suno-api.json b/src/app/docs/swagger-suno-api.json index 064fc42..9019d63 100644 --- a/src/app/docs/swagger-suno-api.json +++ b/src/app/docs/swagger-suno-api.json @@ -3,7 +3,7 @@ "info": { "title": "suno-api", "description": "Use API to call the music generation service of Suno.ai and easily integrate it into agents like GPTs.", - "version": "1.0.0" + "version": "1.1.0" }, "tags": [ { @@ -137,6 +137,56 @@ } } }, + "/api/generate_lyrics": { + "post": { + "summary": "Generate lyrics based on Prompt.", + "description": "Generate lyrics based on Prompt.", + "tags": ["default"], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["prompt"], + "properties": { + "prompt": { + "type": "string", + "description": "Prompt", + "example": "A soothing lullaby" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Lyrics" + }, + "title": { + "type": "string", + "description": "music title" + }, + "status": { + "type": "string", + "description": "Status" + } + } + } + } + } + } + } + } + }, "/api/get": { "get": { "summary": "Get audio information", @@ -146,8 +196,8 @@ { "in": "query", "name": "ids", - "description": "Audio IDs, separated by commas.", - "required": true, + "description": "Audio IDs, separated by commas. Leave blank to return a list of all music.", + "required": false, "schema": { "type": "string" } @@ -297,7 +347,7 @@ } }, "title": "audio_info", - "description": "audio info" + "description": "Audio Info" } } } diff --git a/src/app/page.tsx b/src/app/page.tsx index 77b2b5c..cb38d45 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,23 +7,24 @@ export default function Home() { const markdown = ` --- -## Introduction +## 👋 Introduction Suno.ai v3 is an amazing AI music service. Although the official API is not yet available, we couldn't wait to integrate its capabilities somewhere. We discovered that some users have similar needs, so we decided to open-source this project, hoping you'll like it. -👉 We update quickly, please Star us on Github: [github.com/gcui-art/suno-api](https://github.com/gcui-art/suno-api) +We update quickly, please star us on Github: [github.com/gcui-art/suno-api](https://github.com/gcui-art/suno-api) ⭐ -## Features +## 🌟 Features - Perfectly implements the creation API from \`app.suno.ai\` +- Automatically keep the account active. - Supports \`Custom Mode\` - One-click deployment to Vercel - In addition to the standard API, it also adapts to the API Schema of Agent platforms like GPTs and Coze, so you can use it as a tool/plugin/Action for LLMs and integrate it into any AI Agent. - Permissive open-source license, allowing you to freely integrate and modify. -## Getting Started +## 🚀 Getting Started ### 1. Obtain the cookie of your app.suno.ai account @@ -60,9 +61,9 @@ npm install - If you’re running this locally, be sure to add the following to your \`.env\` file: -\`\`\`bash -SUNO_COOKIE= -\`\`\` + \`\`\`bash + SUNO_COOKIE= + \`\`\` ### 4. Run suno-api @@ -74,14 +75,14 @@ SUNO_COOKIE= - Visit the \`http://localhost:3000/api/get_limit\` API for testing. - If the following result is returned: -\`\`\`json -{ - "credits_left": 50, - "period": "day", - "monthly_limit": 50, - "monthly_usage": 50 -} -\`\`\` + \`\`\`json + { + "credits_left": 50, + "period": "day", + "monthly_limit": 50, + "monthly_usage": 50 + } + \`\`\` it means the program is running normally. @@ -89,7 +90,7 @@ it means the program is running normally. You can check out the detailed API documentation at [suno.gcui.art/docs](https://suno.gcui.art/docs). -## API Reference +## 📚 API Reference Suno API currently mainly implements the following APIs: @@ -97,7 +98,9 @@ Suno API currently mainly implements the following APIs: - \`/api/generate\`: Generate music - \`/api/custom_generate\`: Generate music (Custom Mode, support setting lyrics, music style, title, etc.) -- \`/api/get\`: Get music Info +- \`/api/generate_lyrics\`: Generate lyrics based on prompt +- \`/api/get\`: Get music list +- \`/api/get?ids=\`: Get music Info by id, separate multiple id with ",". - \`/api/get_limit\`: Get quota Info \`\`\` diff --git a/src/lib/SunoApi.ts b/src/lib/SunoApi.ts index 0934336..b77b98c 100644 --- a/src/lib/SunoApi.ts +++ b/src/lib/SunoApi.ts @@ -20,7 +20,7 @@ export interface AudioInfo { gpt_description_prompt?: string; // Prompt for GPT description prompt?: string; // Prompt for audio generation status: string; // Status - type?: string; + type?: string; tags?: string; // Genre of music. duration?: string; // Duration of the audio } @@ -66,12 +66,11 @@ class SunoApi { const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_clerk_js_version=4.70.5`; // Get session ID const sessionResponse = await this.client.get(getSessionUrl); - const sid = sessionResponse.data.response['last_active_session_id']; - if (!sid) { - throw new Error("Failed to get session id"); + if (!sessionResponse?.data?.response?.['last_active_session_id']) { + throw new Error("Failed to get session id, you may need to update the SUNO_COOKIE"); } // Save session ID for later use - this.sid = sid; + this.sid = sessionResponse.data.response['last_active_session_id']; } /** @@ -234,6 +233,28 @@ class SunoApi { } } + /** + * Generates lyrics based on a given prompt. + * @param prompt The prompt for generating lyrics. + * @returns The generated lyrics text. + */ + public async generateLyrics(prompt: string): Promise { + await this.keepAlive(false); + // Initiate lyrics generation + const generateResponse = await this.client.post(`${SunoApi.BASE_URL}/api/generate/lyrics/`, { prompt }); + const generateId = generateResponse.data.id; + + // Poll for lyrics completion + let lyricsResponse = await this.client.get(`${SunoApi.BASE_URL}/api/generate/lyrics/${generateId}`); + while (lyricsResponse?.data?.status !== 'complete') { + await sleep(2); // Wait for 2 seconds before polling again + lyricsResponse = await this.client.get(`${SunoApi.BASE_URL}/api/generate/lyrics/${generateId}`); + } + + // Return the generated lyrics text + return lyricsResponse.data; + } + /** * Processes the lyrics (prompt) from the audio metadata into a more readable format. * @param prompt The original lyrics text.