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/lib/SunoApi.ts b/src/lib/SunoApi.ts index f332b0a..2caeeed 100644 --- a/src/lib/SunoApi.ts +++ b/src/lib/SunoApi.ts @@ -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. *