From ee1cd3bbe54bd77e6b457c29b42a48fb733bb3d3 Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Tue, 1 Sep 2026 02:24:52 +0000 Subject: [PATCH] Correcao do Musicando Ideias: timeout de gateway 502, arranjo vocal/instrumental sintetizado e tratamento de erro JSON --- public/app.js | 7 ++- server.js | 169 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 130 insertions(+), 46 deletions(-) diff --git a/public/app.js b/public/app.js index 729f1a7..6e06474 100644 --- a/public/app.js +++ b/public/app.js @@ -7610,11 +7610,14 @@ window.toggleCustomAudio = (btn) => { turmaId: musicaTurmaSelect?.value || null, gerarAudio: true }) - }); + if (!res.ok) { + const errMsg = await safeExtractError(res, 'Erro ao compor música'); + throw new Error(errMsg); + } const data = await res.json(); - if (res.ok && data.success) { + if (data.success) { activeMusicaData = { titulo: data.titulo || `Música: ${tema}`, versaoPrincipal: data.versaoPrincipal, diff --git a/server.js b/server.js index 6d75a86..cf18e4c 100644 --- a/server.js +++ b/server.js @@ -2529,58 +2529,139 @@ Retorne ESTRITAMENTE um objeto JSON válido (sem tags markdown extras) com a seg }; } - // 4. GERAÇÃO DE ÁUDIO CANTADO (MiniMax music-2.0) + // 4. GERAÇÃO DE ÁUDIO CANTADO / INSTRUMENTALIZADO let audioUrl = null; - if (gerarAudio !== false && process.env.MINIMAX_API_KEY) { + if (gerarAudio !== false) { try { - console.log('[Musicando] Gerando áudio cantado com MiniMax music-2.0...'); - let voiceDesc = 'sweet native brazilian female voice, warm kindergarten teacher singing voice'; - if (voz === 'homem') { - voiceDesc = 'gentle warm native brazilian male voice, friendly acoustic singer'; - } else if (voz === 'crianca') { - voiceDesc = 'playful cute young brazilian child singing voice, energetic kid vocals'; - } - - const audioPrompt = `${ritmo || 'Cantiga de roda'}, ${instrumentos || 'acoustic guitar, percussion'}, ${voiceDesc}, educational children song, clear pt-BR pronunciation`; + console.log('[Musicando] Iniciando geração de áudio musical...'); const lyricsToSing = (musicaData.versaoPrincipal || musicaData.versaoCurta || tema).replace(/\(.*?\)/g, '').trim(); - const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, ''); - const musicResp = await fetch(`${minmBase}/v1/music_generation`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: 'music-2.0', - prompt: audioPrompt, - lyrics: lyricsToSing.substring(0, 500), - audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3' } - }) - }); - - if (musicResp.ok) { - const mData = await musicResp.json(); - const audioHex = mData.data?.audio; - if (audioHex) { - const fs = require('fs'); - const path = require('path'); - const mediaDir = path.join(__dirname, 'public', 'generated-media'); - if (!fs.existsSync(mediaDir)) { - fs.mkdirSync(mediaDir, { recursive: true }); + // Tentativa 1: MiniMax music-2.0 com timeout seguro de 20s (evita 502 de Gateway Proxy) + if (process.env.MINIMAX_API_KEY) { + try { + let voiceDesc = 'sweet native brazilian female voice, warm kindergarten teacher singing voice'; + if (voz === 'homem') { + voiceDesc = 'gentle warm native brazilian male voice, friendly acoustic singer'; + } else if (voz === 'crianca') { + voiceDesc = 'playful cute young brazilian child singing voice, energetic kid vocals'; } - const fileName = `musica_${Date.now()}.mp3`; - const filePath = path.join(mediaDir, fileName); - const audioBuffer = Buffer.from(audioHex, 'hex'); - fs.writeFileSync(filePath, audioBuffer); - await backupMediaFile(filePath, audioBuffer); - audioUrl = `/generated-media/${fileName}`; + + const audioPrompt = `${ritmo || 'Cantiga de roda'}, ${instrumentos || 'acoustic guitar, percussion'}, ${voiceDesc}, educational children song, clear pt-BR pronunciation`; + const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, ''); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); + + const musicResp = await fetch(`${minmBase}/v1/music_generation`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'music-2.0', + prompt: audioPrompt, + lyrics: lyricsToSing.substring(0, 500), + audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3' } + }), + signal: controller.signal + }); + clearTimeout(timeoutId); + + if (musicResp.ok) { + const mData = await musicResp.json(); + const audioHex = mData.data?.audio; + if (audioHex) { + const fs = require('fs'); + const path = require('path'); + const mediaDir = path.join(__dirname, 'public', 'generated-media'); + if (!fs.existsSync(mediaDir)) { + fs.mkdirSync(mediaDir, { recursive: true }); + } + const fileName = `musica_${Date.now()}.mp3`; + const filePath = path.join(mediaDir, fileName); + const audioBuffer = Buffer.from(audioHex, 'hex'); + fs.writeFileSync(filePath, audioBuffer); + backupMediaFile(filePath, audioBuffer).catch(() => {}); + audioUrl = `/generated-media/${fileName}`; + console.log('[Musicando] Áudio gerado com sucesso via MiniMax music-2.0!'); + } + } else { + console.warn('[Musicando] MiniMax áudio retornou status:', musicResp.status); + } + } catch (minimaxErr) { + console.warn('[Musicando] MiniMax music não respondeu a tempo (' + minimaxErr.message + '), acionando arranjo pedagógico vocal...'); + } + } + + // Tentativa 2: Arranjo Pedagógico Vocal + Instrumental de Alta Velocidade (FFmpeg + MsEdgeTTS) + if (!audioUrl) { + console.log('[Musicando] Sintetizando arranjo pedagógico vocal com melodia instrumental...'); + const fs = require('fs'); + const path = require('path'); + const { exec } = require('child_process'); + const { MsEdgeTTS, OUTPUT_FORMAT } = require('msedge-tts'); + + const mediaDir = path.join(__dirname, 'public', 'generated-media'); + if (!fs.existsSync(mediaDir)) { + fs.mkdirSync(mediaDir, { recursive: true }); + } + + const voiceName = (voz === 'homem') ? 'pt-BR-AntonioNeural' : 'pt-BR-FranciscaNeural'; + const tts = new MsEdgeTTS(); + await tts.setMetadata(voiceName, OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3); + + const rawLyrics = (musicaData.versaoPrincipal || musicaData.versaoCurta || tema).replace(/[\(\)]/g, ''); + const voiceTempPath = path.join(mediaDir, `voice_temp_${Date.now()}_${Math.floor(Math.random()*1000)}.mp3`); + + const { audioStream } = tts.toStream(rawLyrics); + await new Promise((resolve, reject) => { + const ws = fs.createWriteStream(voiceTempPath); + audioStream.pipe(ws); + ws.on('finish', resolve); + ws.on('error', reject); + audioStream.on('error', reject); + }); + + // Selecionar trilha instrumental harmônica de acordo com o ritmo + let bgTrack = 'alegre.mp3'; + const rLower = (ritmo || '').toLowerCase(); + if (rLower.includes('calm') || rLower.includes('ninar') || rLower.includes('suave') || rLower.includes('berço')) { + bgTrack = 'calma.mp3'; + } else if (rLower.includes('circo') || rLower.includes('palha')) { + bgTrack = 'circo.mp3'; + } else if (rLower.includes('festa') || rLower.includes('balão') || rLower.includes('pipoca')) { + bgTrack = 'festa.mp3'; + } else if (rLower.includes('bichinho') || rLower.includes('animal') || rLower.includes('fazenda')) { + bgTrack = 'animais.mp3'; + } else if (rLower.includes('brinquedo') || rLower.includes('fantasia')) { + bgTrack = 'brinquedos.mp3'; + } + + const bgMusicPath = path.join(__dirname, 'public', 'assets', 'audio', bgTrack); + const outFileName = `musica_${Date.now()}_${Math.floor(Math.random()*1000)}.mp3`; + const outFilePath = path.join(mediaDir, outFileName); + + if (fs.existsSync(bgMusicPath) && fs.existsSync(voiceTempPath)) { + await new Promise((resolve) => { + exec(`ffmpeg -y -i "${voiceTempPath}" -stream_loop -1 -i "${bgMusicPath}" -filter_complex "[0:a]volume=1.35[v];[1:a]volume=0.18[bg];[v][bg]amix=inputs=2:duration=first[aout]" -map "[aout]" -c:a libmp3lame -b:a 128k "${outFilePath}"`, () => { + try { if (fs.existsSync(voiceTempPath)) fs.unlinkSync(voiceTempPath); } catch (e) {} + resolve(); + }); + }); + } else if (fs.existsSync(voiceTempPath)) { + fs.renameSync(voiceTempPath, outFilePath); + } + + if (fs.existsSync(outFilePath)) { + const buf = fs.readFileSync(outFilePath); + backupMediaFile(outFilePath, buf).catch(() => {}); + audioUrl = `/generated-media/${outFileName}`; + console.log('[Musicando] Arranjo pedagógico concluído com sucesso!'); } - } else { - console.warn('[Musicando] Falha MiniMax áudio:', await musicResp.text()); } } catch (audioErr) { - console.error('[Musicando] Erro ao gerar áudio MiniMax:', audioErr.message); + console.error('[Musicando] Erro na síntese de áudio:', audioErr.message); } }