From 15bab025517a43c1701481c91c241e8f8ec57481 Mon Sep 17 00:00:00 2001 From: tmirkovic Date: Sun, 10 Nov 2024 14:02:40 -0500 Subject: [PATCH] Add route for getting aligned lyrics --- README.md | 1 + README_CN.md | 1 + src/app/api/get_aligned_lyrics/route.ts | 60 +++++++++++++++++++++++++ src/app/docs/page.tsx | 1 + src/app/docs/swagger-suno-api.json | 23 ++++++++++ src/app/page.tsx | 1 + src/lib/SunoApi.ts | 23 +++++++++- 7 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/app/api/get_aligned_lyrics/route.ts diff --git a/README.md b/README.md index 1139ebe..ae62c25 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ Suno API currently mainly implements the following APIs: - `/api/get_limit`: Get quota Info - `/api/extend_audio`: Extend audio length - `/api/generate_stems`: Make stem tracks (separate audio and music track) +- `/api/get_aligned_lyrics`: Get list of timestamps for each word in the lyrics - `/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 c798a7a..eee70fe 100644 --- a/README_CN.md +++ b/README_CN.md @@ -124,6 +124,7 @@ Suno API 目前主要实现了以下 API: - `/api/get_limit`: 获取配额信息 - `/api/extend_audio`: 在一首音乐的基础上,扩展音乐长度 - `/api/generate_stems`: 制作主干轨道(单独的音频和音乐轨道 +- `/api/get_aligned_lyrics`: 获取歌词中每个单词的时间戳列表 - `/api/clip`: 检索特定音乐的信息 - `/api/concat`: 合并音乐,将扩展后的音乐和原始音乐合并 ``` diff --git a/src/app/api/get_aligned_lyrics/route.ts b/src/app/api/get_aligned_lyrics/route.ts new file mode 100644 index 0000000..11681e4 --- /dev/null +++ b/src/app/api/get_aligned_lyrics/route.ts @@ -0,0 +1,60 @@ +import { NextResponse, NextRequest } from "next/server"; +import { sunoApi } from "@/lib/SunoApi"; +import { corsHeaders } from "@/lib/utils"; + +export const dynamic = "force-dynamic"; + +export async function GET(req: NextRequest) { + if (req.method === 'GET') { + try { + const url = new URL(req.url); + const song_id = url.searchParams.get('song_id'); + + if (!song_id) { + return new NextResponse(JSON.stringify({ error: 'Song ID is required' }), { + status: 400, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders + } + }); + } + + const lyricAlignment = await (await sunoApi).getLyricAlignment(song_id); + + + return new NextResponse(JSON.stringify(lyricAlignment), { + status: 200, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders + } + }); + } catch (error) { + console.error('Error fetching lyric alignment:', error); + + return new NextResponse(JSON.stringify({ error: 'Internal server error. ' + error }), { + status: 500, + headers: { + 'Content-Type': 'application/json', + ...corsHeaders + } + }); + } + } else { + return new NextResponse('Method Not Allowed', { + headers: { + Allow: 'GET', + ...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 12511a5..379201c 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/generate_stems\`: Make stem tracks (separate audio and music track) +- \`/api/get_aligned_lyrics\`: Get list of timestamps for each word in the lyrics - \`/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 e31a3e9..5aa47e8 100644 --- a/src/app/docs/swagger-suno-api.json +++ b/src/app/docs/swagger-suno-api.json @@ -416,6 +416,29 @@ } } }, + "/api/get_aligned_lyrics": { + "get": { + "summary": "Get lyric alignment.", + "description": "Get lyric alignment.", + "tags": ["default"], + "parameters": [ + { + "name": "song_id", + "in": "query", + "required": true, + "description": "Song ID", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/schemas/audio_info" + } + } + } + }, "/api/clip": { "get": { "summary": "Get clip information based on ID.", diff --git a/src/app/page.tsx b/src/app/page.tsx index 19ef676..89f7f4a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -107,6 +107,7 @@ Suno API currently mainly implements the following APIs: - \`/api/get_limit\`: Get quota Info - \`/api/extend_audio\`: Extend audio length - \`/api/generate_stems\`: Make stem tracks (separate audio and music track) +- \`/api/get_aligned_lyrics\`: Get list of timestamps for each word in the lyrics - \`/api/concat\`: Generate the whole song from extensions \`\`\` diff --git a/src/lib/SunoApi.ts b/src/lib/SunoApi.ts index 7fe55c9..af6733d 100644 --- a/src/lib/SunoApi.ts +++ b/src/lib/SunoApi.ts @@ -389,14 +389,15 @@ class SunoApi { * @returns A promise that resolves to an AudioInfo object representing the generated stems. */ public async generateStems(song_id: string): Promise { + await this.keepAlive(false); 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, @@ -404,6 +405,26 @@ class SunoApi { })); } + + /** + * Get the lyric alignment for a song. + * @param song_id The ID of the song to get the lyric alignment for. + * @returns A promise that resolves to an object containing the lyric alignment. + */ + public async getLyricAlignment(song_id: string): Promise { + await this.keepAlive(false); + const response = await this.client.get(`${SunoApi.BASE_URL}/api/gen/${song_id}/aligned_lyrics/v2/`); + + console.log(`getLyricAlignment ~ response:`, response.data); + return response.data?.aligned_words.map((transcribedWord: any) => ({ + word: transcribedWord.word, + start_s: transcribedWord.start_s, + end_s: transcribedWord.end_s, + success: transcribedWord.success, + p_align: transcribedWord.p_align + })); + } + /** * Processes the lyrics (prompt) from the audio metadata into a more readable format. * @param prompt The original lyrics text.