diff --git a/README.md b/README.md index f4da436..37e7fb0 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ Suno API currently mainly implements the following APIs: - `/api/get_limit`: Get quota Info - `/api/extend_audio`: Extend audio length - `/api/clip`: Get clip information based on ID passed as query parameter `id` +- `/api/concat`: Generate the whole song from extensions ``` For more detailed documentation, please check out the demo site: @@ -170,12 +171,17 @@ def get_quota_information(): response = requests.get(url) return response.json() - def get_clip(clip_id): url = f"{base_url}/api/clip?id={clip_id}" response = requests.get(url) return response.json() +def generate_whole_song(clip_id): + payloyd = {"clip_id": clip_id} + url = f"{base_url}/api/concat" + response = requests.post(url, json=payload) + return response.json() + if __name__ == '__main__': data = generate_audio_by_prompt({ diff --git a/src/app/api/concat/route.ts b/src/app/api/concat/route.ts new file mode 100644 index 0000000..6bf6e4b --- /dev/null +++ b/src/app/api/concat/route.ts @@ -0,0 +1,64 @@ +import { NextResponse, NextRequest } from "next/server"; +import { sunoApi } from "@/lib/SunoApi"; +import { corsHeaders } from "@/lib/utils"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: NextRequest) { + if (req.method === 'POST') { + try { + const body = await req.json(); + const { clip_id } = body; + if (!clip_id) { + return new NextResponse(JSON.stringify({ error: 'Clip id is required' }), { + status: 400, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders + } + }); + } + const audioInfo = await (await sunoApi).concatenate(clip_id); + return new NextResponse(JSON.stringify(audioInfo), { + status: 200, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders + } + }); + } catch (error: any) { + console.error('Error generating concatenating audio:', 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', + ...corsHeaders + } + }); + } + return new NextResponse(JSON.stringify({ error: 'Internal server error' }), { + status: 500, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders + } + }); + } + } else { + return new NextResponse('Method Not Allowed', { + headers: { + Allow: 'POST', + ...corsHeaders + }, + status: 405 + }); + } +} + +export async function OPTIONS(request: Request) { + return new Response(null, { + status: 200, + headers: corsHeaders + }); +} diff --git a/src/app/api/custom_generate/route.ts b/src/app/api/custom_generate/route.ts index 12e9964..c613803 100644 --- a/src/app/api/custom_generate/route.ts +++ b/src/app/api/custom_generate/route.ts @@ -65,4 +65,4 @@ export async function OPTIONS(request: Request) { status: 200, headers: corsHeaders }); -} \ No newline at end of file +} diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index 24205ba..a053eed 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -30,6 +30,7 @@ export default function Docs() { - \`/api/get_limit\`: Get quota Info - \`/api/extend_audio\`: Extend audio length - \`/api/clip\`: Get clip information based on ID passed as query parameter \`id\` +- \`/api/concat\`: Generate the whole song from extensions \`\`\` Feel free to explore the detailed API parameters and conduct tests on this page. diff --git a/src/app/page.tsx b/src/app/page.tsx index 2d87bda..0d39d36 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -106,6 +106,7 @@ Suno API currently mainly implements the following APIs: - \`/api/get?ids=\`: Get music Info by id, separate multiple id with ",". - \`/api/get_limit\`: Get quota Info - \`/api/extend_audio\`: Extend audio length +- \`/api/concat\`: Generate the whole song from extensions \`\`\` For more detailed documentation, please check out the demo site: diff --git a/src/lib/SunoApi.ts b/src/lib/SunoApi.ts index 4a9b2d1..09515e4 100644 --- a/src/lib/SunoApi.ts +++ b/src/lib/SunoApi.ts @@ -63,7 +63,7 @@ class SunoApi { */ private async getAuthToken() { // URL to get session ID - const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_clerk_js_version=4.72.1`; + const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_clerk_js_version=4.72.4`; // Get session ID const sessionResponse = await this.client.get(getSessionUrl); if (!sessionResponse?.data?.response?.['last_active_session_id']) { @@ -82,7 +82,7 @@ class SunoApi { throw new Error("Session ID is not set. Cannot renew token."); } // URL to renew session token - const renewUrl = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/${this.sid}/tokens?_clerk_js_version=4.72.0-snapshot.vc141245`; + const renewUrl = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/${this.sid}/tokens?_clerk_js_version==4.72.4`; // Renew session token const renewResponse = await this.client.post(renewUrl); logger.info("KeepAlive...\n"); @@ -116,6 +116,29 @@ class SunoApi { return audios; } + /** + * Calls the concatenate endpoint for a clip to generate the whole song. + * @param clip_id The ID of the audio clip to concatenate. + * @returns A promise that resolves to an AudioInfo object representing the concatenated audio. + * @throws Error if the response status is not 200. + */ + public async concatenate(clip_id: string): Promise { + await this.keepAlive(false); + const payload: any = { clip_id: clip_id }; + + const response = await this.client.post( + `${SunoApi.BASE_URL}/api/generate/concat/v2/`, + payload, + { + timeout: 10000, // 10 seconds timeout + }, + ); + if (response.status !== 200) { + throw new Error("Error response:" + response.statusText); + } + return response.data; + } + /** * Generates custom audio based on provided parameters. *