From 96b675a0391030fa529dd4c6f7138066b8af199e Mon Sep 17 00:00:00 2001 From: tmirkovic Date: Sat, 9 Nov 2024 22:44:30 -0500 Subject: [PATCH] Add route for generating stems --- README.md | 1 + README_CN.md | 1 + src/app/api/generate_stems/route.ts | 69 +++++++++++++++++++++++++++++ src/app/docs/page.tsx | 1 + src/app/docs/swagger-suno-api.json | 29 ++++++++++++ src/app/page.tsx | 1 + src/lib/SunoApi.ts | 21 +++++++++ 7 files changed, 123 insertions(+) create mode 100644 src/app/api/generate_stems/route.ts diff --git a/README.md b/README.md index edb4163..1139ebe 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ Suno API currently mainly implements the following APIs: If no IDs are provided, all music will be returned. - `/api/get_limit`: Get quota Info - `/api/extend_audio`: Extend audio length +- `/api/generate_stems`: Make stem tracks (separate audio and music track) - `/api/clip`: Get clip information based on ID passed as query parameter `id` - `/api/concat`: Generate the whole song from extensions ``` diff --git a/README_CN.md b/README_CN.md index 5c15789..c798a7a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -123,6 +123,7 @@ Suno API 目前主要实现了以下 API: - `/api/get`: 根据id获取音乐信息。获取多个请用","分隔,不传ids则返回所有音乐 - `/api/get_limit`: 获取配额信息 - `/api/extend_audio`: 在一首音乐的基础上,扩展音乐长度 +- `/api/generate_stems`: 制作主干轨道(单独的音频和音乐轨道 - `/api/clip`: 检索特定音乐的信息 - `/api/concat`: 合并音乐,将扩展后的音乐和原始音乐合并 ``` diff --git a/src/app/api/generate_stems/route.ts b/src/app/api/generate_stems/route.ts new file mode 100644 index 0000000..7070d90 --- /dev/null +++ b/src/app/api/generate_stems/route.ts @@ -0,0 +1,69 @@ +import { NextResponse, NextRequest } from "next/server"; +import { DEFAULT_MODEL, 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 { audio_id } = body; + + if (!audio_id) { + return new NextResponse(JSON.stringify({ error: 'Audio ID is required' }), { + status: 400, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders + } + }); + } + + const audioInfo = await (await sunoApi) + .generateStems(audio_id); + + return new NextResponse(JSON.stringify(audioInfo), { + status: 200, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders + } + }); + } catch (error: any) { + console.error('Error generating stems:', 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', + ...corsHeaders + } + }); + } + return new NextResponse(JSON.stringify({ error: 'Internal server error: ' + JSON.stringify(error.response.data.detail) }), { + 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 + }); +} \ No newline at end of file diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index a053eed..12511a5 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -29,6 +29,7 @@ export default function Docs() { ids. If no IDs are provided, all music will be returned. - \`/api/get_limit\`: Get quota Info - \`/api/extend_audio\`: Extend audio length +- \`/api/generate_stems\`: Make stem tracks (separate audio and music track) - \`/api/clip\`: Get clip information based on ID passed as query parameter \`id\` - \`/api/concat\`: Generate the whole song from extensions \`\`\` diff --git a/src/app/docs/swagger-suno-api.json b/src/app/docs/swagger-suno-api.json index d00b676..e31a3e9 100644 --- a/src/app/docs/swagger-suno-api.json +++ b/src/app/docs/swagger-suno-api.json @@ -243,6 +243,35 @@ } } }, + "/api/generate_stems": { + "post": { + "summary": "Make stem tracks (separate audio and music track).", + "description": "Make stem tracks (separate audio and music track).", + "tags": ["default"], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["audio_id"], + "properties": { + "audio_id": { + "type": "string", + "description": "The ID of the song to generate stems for.", + "example": "e76498dc-6ab4-4a10-a19f-8a095790e28d" + } + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/schemas/audio_info" + } + } + } + }, "/api/generate_lyrics": { "post": { "summary": "Generate lyrics based on Prompt.", diff --git a/src/app/page.tsx b/src/app/page.tsx index b945dff..19ef676 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/generate_stems\`: Make stem tracks (separate audio and music track) - \`/api/concat\`: Generate the whole song from extensions \`\`\` diff --git a/src/lib/SunoApi.ts b/src/lib/SunoApi.ts index a7018da..7fe55c9 100644 --- a/src/lib/SunoApi.ts +++ b/src/lib/SunoApi.ts @@ -383,6 +383,27 @@ class SunoApi { return response.data; } + /** + * Generate stems for a song. + * @param song_id The ID of the song to generate stems for. + * @returns A promise that resolves to an AudioInfo object representing the generated stems. + */ + public async generateStems(song_id: string): Promise { + const response = await this.client.post( + `${SunoApi.BASE_URL}/api/edit/stems/${song_id}`, {} + ); + console.log('generateStems response:\n', response?.data); + return response.data.clips.map((clip: any) => ({ + id: clip.id, + status: clip.status, + metadata: clip.metadata, + created_at: clip.created_at, + title: clip.title, + stem_from_id: clip.metadata.stem_from_id, + duration: clip.metadata.duration + })); + } + /** * Processes the lyrics (prompt) from the audio metadata into a more readable format. * @param prompt The original lyrics text.