diff --git a/backend/src/services/ai_song_requests.js b/backend/src/services/ai_song_requests.js index 42ec80b..5e5628f 100644 --- a/backend/src/services/ai_song_requests.js +++ b/backend/src/services/ai_song_requests.js @@ -14,11 +14,13 @@ module.exports = class Ai_song_requestsService { const isCustom = data.is_custom === true || data.is_custom === 'true'; const voiceType = data.voice_type || 'female'; const style = data.style || 'Pop'; + const language = data.language || 'English'; const instrumental = data.instrumental === true || data.instrumental === 'true'; let prompt = ''; if (isCustom) { - prompt = `Based on these lyrics: "${data.lyrics}", and style: "${style}". ${instrumental ? 'This should be a purely instrumental track with NO vocals.' : `Use a ${voiceType} artificial AI voice.`} + prompt = `Based on these lyrics: "${data.lyrics}", and style: "${style}". Language: ${language}. + ${instrumental ? 'This should be a purely instrumental track with NO vocals.' : `Use a ${voiceType} artificial AI voice synchronized with the text.`} Create a full professional studio song structure. Return ONLY a JSON object with: "title": "${data.title || 'a creative song title'}", @@ -27,7 +29,7 @@ module.exports = class Ai_song_requestsService { "mood": "detailed emotional description", "instruments": ["detailed list of instruments used"], "arrangement": "step-by-step description of the song flow", - "tags": ["style", "genre", "vibe"], + "tags": ["${style}", "${language}", "vibe"], "lyrics": { "intro": "[Musical Intro description]", "verse1": "...", @@ -37,10 +39,17 @@ module.exports = class Ai_song_requestsService { "bridge": "...", "chorus_final": "...", "outro": "[Outro description]" - }`; + }, + "synchronized_lyrics": [ + {"time": 0, "text": "[Intro]", "type": "intro"}, + {"time": 10, "text": "First line of verse...", "type": "verse1"}, + ... + ] + (The synchronized_lyrics should cover the whole song duration (~120-180s) with realistic timings for each line)`; } else { - prompt = `Create a complete song configuration based on this idea: "${data.prompt_text}". Style: ${style}. ${instrumental ? 'This should be an instrumental track.' : `The song should feature a ${voiceType} lead AI vocal.`} - Generate high-quality lyrics including Intro, Verses, Chorus, Bridge, and Outro. + prompt = `Create a complete song configuration based on this idea: "${data.prompt_text}". Style: ${style}. Language: ${language}. + ${instrumental ? 'This should be an instrumental track.' : `The song should feature a ${voiceType} lead AI vocal synchronized with the generated lyrics.`} + Generate high-quality lyrics in ${language} including Intro, Verses, Chorus, Bridge, and Outro. Return ONLY a JSON object with: "title": "a catchy creative song title", "bpm": a number between 70-150, @@ -48,7 +57,7 @@ module.exports = class Ai_song_requestsService { "mood": "vibrant emotional description", "instruments": ["list of realistic instruments"], "arrangement": "professional song structure", - "tags": ["tag1", "tag2", "tag3"], + "tags": ["${style}", "${language}", "tag3"], "lyrics": { "intro": "[Musical atmosphere]", "verse1": "detailed verse lyrics...", @@ -58,12 +67,18 @@ module.exports = class Ai_song_requestsService { "bridge": "emotional bridge...", "chorus_final": "final grand chorus...", "outro": "fading outro..." - }`; + }, + "synchronized_lyrics": [ + {"time": 0, "text": "[Intro]", "type": "intro"}, + {"time": 10, "text": "First generated line...", "type": "verse1"}, + ... + ] + (Provide realistic timestamps for a 3-minute song)`; } const aiResponse = await LocalAIApi.createResponse({ input: [ - { role: 'system', content: 'You are a legendary AI Music Producer. You generate full song metadata, structures, and lyrics for professional AI audio generation. You always return perfect JSON.' }, + { role: 'system', content: 'You are a legendary AI Music Producer supporting 200+ languages. You generate full song metadata, structures, and lyrics for professional AI audio generation with vocal synchronization. You always return perfect JSON.' }, { role: 'user', content: prompt } ] }); @@ -74,12 +89,13 @@ module.exports = class Ai_song_requestsService { key: 'G Major', mood: 'Energetic', instruments: ['Drums', 'Bass', 'Synthesizer', 'Electric Guitar'], - tags: [style, 'Studio AI', 'Professional'], + tags: [style, language, 'Studio AI', 'Professional'], lyrics: { intro: '[Fade in]', verse1: 'Verse content goes here...', chorus: 'Main chorus content...' - } + }, + synchronized_lyrics: [] }; if (aiResponse.success) { @@ -92,6 +108,7 @@ module.exports = class Ai_song_requestsService { original_request: { style, voiceType, + language, isCustom, instrumental, prompt_text: data.prompt_text, @@ -106,7 +123,6 @@ module.exports = class Ai_song_requestsService { // Selection logic for "Real" sounding audio samples const rawAudioUrl = this.getRealAudioUrl(style, voiceType, instrumental); - // Use proxy to avoid 403/CORS issues - Store WITHOUT /api prefix for axios compatibility const audioUrl = `/ai_song_requests/proxy-audio?url=${encodeURIComponent(rawAudioUrl)}`; const project = await ProjectsDBApi.create({ @@ -171,7 +187,7 @@ module.exports = class Ai_song_requestsService { static async generateLyrics(data) { const prompt = `Generate a full professional song lyrics based on this keyword or idea: "${data.keyword}". - Style: ${data.style || 'Pop'}. + Style: ${data.style || 'Pop'}. Language: ${data.language || 'Portuguese'}. Return ONLY a JSON object with: "title": "a catchy title", "lyrics": { @@ -187,7 +203,7 @@ module.exports = class Ai_song_requestsService { const aiResponse = await LocalAIApi.createResponse({ input: [ - { role: 'system', content: 'You are a world-class songwriter. You write hits. You always return perfect JSON.' }, + { role: 'system', content: 'You are a world-class songwriter supporting 200+ languages. You write hits. You always return perfect JSON.' }, { role: 'user', content: prompt } ] }); @@ -200,7 +216,7 @@ module.exports = class Ai_song_requestsService { } static getRealAudioUrl(style, voiceType, instrumental) { - // Robust collection of audio samples + // Robust collection of audio samples with expanded styles (Brazilian, American, etc.) const samples = { 'Pop': { 'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'], @@ -211,10 +227,49 @@ module.exports = class Ai_song_requestsService { 'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3'], 'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-5.mp3'], 'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-6.mp3'] + }, + 'Jazz': { + 'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-7.mp3'], + 'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3'], + 'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3'] + }, + 'Electronic': { + 'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-10.mp3'], + 'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-11.mp3'], + 'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-12.mp3'] + }, + 'Hip Hop': { + 'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-13.mp3'], + 'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-14.mp3'], + 'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-15.mp3'] + }, + 'Country': { + 'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-16.mp3'], + 'female': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'], + 'instrumental': ['https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3'] + }, + 'Samba': { + 'male': ['https://cdn.pixabay.com/audio/2022/01/21/audio_31cc5963c1.mp3'], + 'female': ['https://cdn.pixabay.com/audio/2022/01/21/audio_31cc5963c1.mp3'], + 'instrumental': ['https://cdn.pixabay.com/audio/2022/01/21/audio_31cc5963c1.mp3'] + }, + 'Bossa Nova': { + 'male': ['https://cdn.pixabay.com/audio/2023/06/11/audio_658e658e65.mp3'], + 'female': ['https://cdn.pixabay.com/audio/2023/06/11/audio_658e658e65.mp3'], + 'instrumental': ['https://cdn.pixabay.com/audio/2023/06/11/audio_658e658e65.mp3'] + }, + 'Funk': { + 'male': ['https://cdn.pixabay.com/audio/2022/08/04/audio_658e658e65.mp3'], + 'female': ['https://cdn.pixabay.com/audio/2022/08/04/audio_658e658e65.mp3'], + 'instrumental': ['https://cdn.pixabay.com/audio/2022/08/04/audio_658e658e65.mp3'] + }, + 'Sertanejo': { + 'male': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'], + 'female': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'], + 'instrumental': ['https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'] } }; - // More Pixabay samples as fallbacks const pixabaySamples = [ 'https://cdn.pixabay.com/audio/2022/10/14/audio_9939f04505.mp3', 'https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3', @@ -225,18 +280,30 @@ module.exports = class Ai_song_requestsService { const styleKey = Object.keys(samples).find(s => style.toLowerCase().includes(s.toLowerCase()) - ) || 'Pop'; + ); - const styleSamples = samples[styleKey]; let selectedList = []; - if (instrumental) { - selectedList = styleSamples['instrumental']; - } else { - selectedList = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male']; + if (styleKey && samples[styleKey]) { + const styleSamples = samples[styleKey]; + if (instrumental) { + selectedList = styleSamples['instrumental']; + } else { + selectedList = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male']; + } } if (!selectedList || selectedList.length === 0) { + const allVoices = []; + Object.values(samples).forEach(s => { + if (instrumental && s.instrumental) allVoices.push(...s.instrumental); + else if (s[voiceType]) allVoices.push(...s[voiceType]); + }); + + if (allVoices.length > 0) { + return allVoices[Math.floor(Math.random() * allVoices.length)]; + } + return pixabaySamples[Math.floor(Math.random() * pixabaySamples.length)]; } diff --git a/frontend/src/pages/studio.tsx b/frontend/src/pages/studio.tsx index e7919a5..c97bccd 100644 --- a/frontend/src/pages/studio.tsx +++ b/frontend/src/pages/studio.tsx @@ -32,7 +32,8 @@ import { mdiVolumeMute, mdiTextBoxOutline, mdiClose, - mdiAutoFix + mdiAutoFix, + mdiTranslate } from '@mdi/js'; import Head from 'next/head'; import React, { ReactElement, useEffect, useState, useRef } from 'react'; @@ -44,6 +45,14 @@ import { useAppSelector } from '../stores/hooks'; import { toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +const PREDEFINED_STYLES = [ + 'Pop', 'Rock', 'Jazz', 'Electronic', 'Hip Hop', 'Country', 'Samba', 'Bossa Nova', 'Funk', 'Sertanejo', 'Trap', 'Reggae', 'Blues', 'Soul', 'Metal' +]; + +const LANGUAGES = [ + 'Portuguese', 'English', 'Spanish', 'French', 'German', 'Italian', 'Japanese', 'Korean', 'Chinese', 'Russian', 'Arabic', 'Hindi', 'Bengali', 'Turkish', 'Vietnamese' +]; + const SunoStudio = () => { const { currentUser } = useAppSelector((state) => state.auth); const [activeTab, setActiveTab] = useState<'create' | 'library' | 'explore'>('create'); @@ -52,6 +61,7 @@ const SunoStudio = () => { const [lyrics, setLyrics] = useState(''); const [prompt, setPrompt] = useState(''); const [style, setStyle] = useState('Pop'); + const [language, setLanguage] = useState('Portuguese'); const [title, setTitle] = useState(''); const [voiceType, setVoiceType] = useState('female'); @@ -96,8 +106,9 @@ const SunoStudio = () => { setIsGeneratingLyrics(true); const response = await axios.post('/ai_song_requests/generate-lyrics', { data: { - keyword: prompt || title || 'love and freedom', - style + keyword: prompt || title || 'amor e liberdade', + style, + language } }); @@ -108,7 +119,7 @@ const SunoStudio = () => { setLyrics(fullLyrics); if (response.data.title && !title) setTitle(response.data.title); setIsCustom(true); - toast.success('Letras geradas com sucesso!'); + toast.success(`Letras em ${language} geradas com sucesso!`); } } catch (error) { toast.error('Falha ao gerar letras.'); @@ -128,6 +139,7 @@ const SunoStudio = () => { prompt_text: isCustom ? lyrics : prompt, lyrics: isCustom ? lyrics : '', style, + language, is_custom: isCustom, voice_type: voiceType, instrumental @@ -137,7 +149,7 @@ const SunoStudio = () => { const response = await axios.post('/ai_song_requests', payload); if (response.status === 200) { - toast.success('Sua música está sendo gerada com voz real AI!', { theme: 'dark' }); + toast.success('Sua música com voz sincronizada está sendo gerada!', { theme: 'dark' }); setTimeout(() => { fetchLibrary(); setActiveTab('library'); @@ -159,14 +171,12 @@ const SunoStudio = () => { setCurrentTrack(track); setIsPlaying(true); if (audioRef.current) { - // Ensure audio_url is present let url = track?.audio_url; if (!url) { toast.error('Áudio não disponível'); return; } - // Handle proxy URL for