1. Adicione uma opção de escolher em tempo real um...

Criado em: 18 de maio de 2025

Respondido usando O3 por Chat01

Pergunta

  1. Adicione uma opção de escolher em tempo real um botão slide que permite alterar a velocidade do audio
  2. Mostre em tempo o waveform da musica, da posição | que é onde o audio está
  3. Além disso o « Voltar 2 s (←) está bloqueado, não funciona.
html
<!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="utf-8"> <title>Ajustar Legenda Pro v2</title> <style> body{font-family:Arial,Helvetica,sans-serif;margin:20px;} #controls{margin-bottom:1rem;} button{margin-right:.5rem;padding:.4rem .8rem;font-size:14px;} #wordList{display:flex;flex-wrap:wrap;gap:4px;} .word{padding:2px 4px;border-radius:4px;cursor:pointer;border:1px solid transparent;} .word.pending{background:#eee;} .word.current{border:1px solid #000;} .word.recorded{background:#4caf50;color:#fff;} #output{width:100%;height:120px;margin-top:1rem;} </style> </head> <body> <h1>Ajustar Legenda Pro v2</h1> <div id="controls"> <label>SRT: <input type="file" id="srtFile" accept=".srt,.txt"></label> <label>Áudio: <input type="file" id="audioFile" accept="audio/*"></label> <button id="startBtn" disabled>INICIAR</button> <button id="undoBtn" disabled>Desfazer (Z)</button> <button id="backBtn" disabled>&laquo; Voltar 2 s (&larr;)</button> <span id="progress"></span> </div> <div id="wordList"></div> <textarea id="output" placeholder="Linhas geradas..." readonly></textarea> <button id="copyBtn" disabled>Copiar</button> <button id="downloadBtn" disabled>Baixar .srt</button> <audio id="player"></audio> <script> // --- utilidades --- function parseSrt(text){ const lines=text.split(/\r?\n/).filter(Boolean); return lines.map(l=>{ const [time,word]=l.split('#'); const [m,s,ds]=time.split(':').map(Number); return {originalTime:m*60+s+ds/10,word}; }); } function formatTime(sec){ const m=Math.floor(sec/60); const s=Math.floor(sec%60); const ds=Math.round((sec-m*60-s)*10); return `${m}:${s.toString().padStart(2,'0')}:${ds}`; } const srtInput=document.getElementById('srtFile'); const audioInput=document.getElementById('audioFile'); const startBtn=document.getElementById('startBtn'); const undoBtn=document.getElementById('undoBtn'); const backBtn=document.getElementById('backBtn'); const copyBtn=document.getElementById('copyBtn'); const downloadBtn=document.getElementById('downloadBtn'); const player=document.getElementById('player'); const wordListEl=document.getElementById('wordList'); const output=document.getElementById('output'); const progress=document.getElementById('progress'); let words=[]; let currentIdx=0; // --- carregamento de arquivos --- srtInput.addEventListener('change',readSrt); audioInput.addEventListener('change',handleAudio); function readSrt(){ if(!srtInput.files.length) return; const reader=new FileReader(); reader.onload=e=>{ words=parseSrt(e.target.result); renderList(); updateProgress(); if(audioInput.files.length) startBtn.disabled=false; }; reader.readAsText(srtInput.files[0],'utf-8'); } function handleAudio(){ if(!audioInput.files.length) return; player.src=URL.createObjectURL(audioInput.files[0]); if(words.length) startBtn.disabled=false; } // --- renderização --- function renderList(){ wordListEl.innerHTML=''; words.forEach((w,i)=>{ const span=document.createElement('span'); span.textContent=w.word; span.className='word pending'; span.dataset.index=i; span.addEventListener('click',()=>{ const t=w.recordedTime??w.originalTime; player.currentTime=t; player.play(); }); wordListEl.appendChild(span); }); } function markCurrentWord(){ wordListEl.querySelectorAll('.word').forEach(el=>el.classList.remove('current')); const current=wordListEl.querySelector(`.word[data-index="${currentIdx}"]`); if(current) current.classList.add('current'); } function updateProgress(){ const done=words.filter(w=>w.recordedTime!==undefined).length; progress.textContent=`${done} / ${words.length}`; undoBtn.disabled=done===0; copyBtn.disabled=downloadBtn.disabled=done===0; } // --- gravação e comandos --- startBtn.addEventListener('click',()=>{ currentIdx=0; player.currentTime=0; player.play(); markCurrentWord(); }); function exportSrt(){ return words.filter(w=>w.recordedTime!==undefined) .map(w=>`${formatTime(w.recordedTime)}#${w.word}`) .join('\n'); } function undoLast(){ const last=words.map(w=>w.recordedTime!==undefined).lastIndexOf(true); if(last<0) return; delete words[last].recordedTime; const span=wordListEl.querySelector(`.word[data-index="${last}"]`); span.classList.remove('recorded'); span.classList.add('pending'); currentIdx=last; markCurrentWord(); updateProgress(); output.value=exportSrt(); } document.addEventListener('keydown',e=>{ if(e.code==='Space'){ e.preventDefault(); const now=player.currentTime; if(currentIdx>=words.length) return; const item=words[currentIdx]; item.recordedTime=now; const span=wordListEl.querySelector(`.word[data-index="${currentIdx}"]`); span.classList.remove('pending'); span.classList.add('recorded'); currentIdx=Math.min(currentIdx+1,words.length); markCurrentWord(); updateProgress(); if(currentIdx===words.length){ player.pause(); output.value=exportSrt(); } }else if(e.key.toLowerCase()==='z'){ undoLast(); }else if(e.key==='ArrowLeft'){ player.currentTime=Math.max(0,player.currentTime-2); } }); undoBtn.addEventListener('click',undoLast); backBtn.addEventListener('click',()=>{player.currentTime=Math.max(0,player.currentTime-2);}); copyBtn.addEventListener('click',()=>{navigator.clipboard.writeText(output.value);}); downloadBtn.addEventListener('click',()=>{ const blob=new Blob([output.value],{type:'text/plain'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download='ajustada.srt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }); player.addEventListener('ended',()=>{output.value=exportSrt();}); </script> </body> </html>
  • Mande o código completo.

Pensamento

Analyzing user's request

The user is asking for a modified web page that includes a few features. First, they want a slider button for real-time audio speed control. Second, they want to display the audio waveform in real-time, with a position marker. Lastly, there's an issue with the "Voltar 2 s" button, which is blocked because the backBtn is disabled initially — the solution could be enabling the button once the audio is loaded or at the start. I’ll need to fix the backBtn behavior and implement these changes.

Generating modified HTML code

The user wants an updated HTML page that includes a few specific features. The first change is a slider for adjusting audio playback speed, with the display of the current speed. The second change is displaying the waveform of the audio file using the Web Audio API and a canvas element, with a dynamic marker showing current audio position. Finally, the disabled back button needs to be enabled once the audio is loaded. I'll output the code for these features now since browsing or external tools aren't necessary.

Thought for 19 seconds

Resposta

Segue o código completo já com as três melhorias solicitadas:

  • Controle de velocidade em tempo real – um slider (0,5 × – 2,0 ×) altera imediatamente playbackRate do áudio e mostra o valor atual.
  • Waveform dinâmico – um <canvas> exibe a forma de onda do arquivo de áudio; um marcador vertical “|” acompanha a posição atual de reprodução em tempo real.
  • Botão “Voltar 2 s (←)” corrigido – ele agora é habilitado assim que o áudio é carregado ou a reprodução começa.
html
<!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="utf-8"> <title>Ajustar Legenda Pro v2</title> <style> body{font-family:Arial,Helvetica,sans-serif;margin:20px;} #controls{margin-bottom:1rem;display:flex;flex-wrap:wrap;gap:.5rem;align-items:center;} button{padding:.4rem .8rem;font-size:14px;cursor:pointer;} input[type="range"]{vertical-align:middle;} #wordList{display:flex;flex-wrap:wrap;gap:4px;} .word{padding:2px 4px;border-radius:4px;cursor:pointer;border:1px solid transparent;} .word.pending{background:#eee;} .word.current{border:1px solid #000;} .word.recorded{background:#4caf50;color:#fff;} #output{width:100%;height:120px;margin-top:1rem;} #waveform{width:100%;height:100px;border:1px solid #ccc;margin-top:1rem;} </style> </head> <body> <h1>Ajustar Legenda Pro v2</h1> <!-- CONTROLES --> <div id="controls"> <label>SRT: <input type="file" id="srtFile" accept=".srt,.txt"></label> <label>Áudio: <input type="file" id="audioFile" accept="audio/*"></label> <button id="startBtn" disabled>INICIAR</button> <button id="undoBtn" disabled>Desfazer (Z)</button> <button id="backBtn" disabled>&laquo; Voltar 2 s (&larr;)</button> <span id="progress"></span> <!-- SLIDER DE VELOCIDADE --> <label style="margin-left:1rem;">Velocidade: <input type="range" id="speedSlider" min="0.5" max="2" value="1" step="0.1"> <span id="speedValue">1.0&times;</span> </label> </div> <!-- FORMA DE ONDA --> <canvas id="waveform"></canvas> <!-- LISTA DE PALAVRAS / SAÍDA --> <div id="wordList"></div> <textarea id="output" placeholder="Linhas geradas..." readonly></textarea> <button id="copyBtn" disabled>Copiar</button> <button id="downloadBtn" disabled>Baixar .srt</button> <audio id="player"></audio> <script> // ---------- utilidades ---------- function parseSrt(text){ const lines=text.split(/\r?\n/).filter(Boolean); return lines.map(l=>{ const [time,word]=l.split('#'); const [m,s,ds]=time.split(':').map(Number); return {originalTime:m*60+s+ds/10,word}; }); } function formatTime(sec){ const m=Math.floor(sec/60); const s=Math.floor(sec%60); const ds=Math.round((sec-m*60-s)*10); return `${m}:${s.toString().padStart(2,'0')}:${ds}`; } // ---------- elementos ---------- const srtInput = document.getElementById('srtFile'); const audioInput= document.getElementById('audioFile'); const startBtn = document.getElementById('startBtn'); const undoBtn = document.getElementById('undoBtn'); const backBtn = document.getElementById('backBtn'); const copyBtn = document.getElementById('copyBtn'); const downloadBtn= document.getElementById('downloadBtn'); const player = document.getElementById('player'); const wordListEl= document.getElementById('wordList'); const output = document.getElementById('output'); const progress = document.getElementById('progress'); const speedSlider= document.getElementById('speedSlider'); const speedValue = document.getElementById('speedValue'); const waveformCanvas = document.getElementById('waveform'); const wfCtx = waveformCanvas.getContext('2d'); let words=[],currentIdx=0; let audioBuffer=null; // para forma de onda let wfAnimationId=null; // p/ cancelar quando necessário // ---------- carregamento de arquivos ---------- srtInput.addEventListener('change',readSrt); audioInput.addEventListener('change',handleAudio); function readSrt(){ if(!srtInput.files.length) return; const reader=new FileReader(); reader.onload=e=>{ words=parseSrt(e.target.result); renderList(); updateProgress(); if(audioInput.files.length) startBtn.disabled=false; }; reader.readAsText(srtInput.files[0],'utf-8'); } function handleAudio(){ if(!audioInput.files.length) return; const file=audioInput.files[0]; player.src=URL.createObjectURL(file); backBtn.disabled=false; // agora habilita if(words.length) startBtn.disabled=false; // decodificar para desenhar forma de onda const reader=new FileReader(); reader.onload=async e=>{ const arrayBuf=e.target.result; try{ const audioCtx=new (window.AudioContext||window.webkitAudioContext)(); audioBuffer=await audioCtx.decodeAudioData(arrayBuf); drawWaveform(); // desenha uma vez }catch(err){console.error(err);} }; reader.readAsArrayBuffer(file); } // ---------- forma de onda ---------- function drawWaveform(){ if(!audioBuffer) return; // garantir o tamanho do canvas waveformCanvas.width=waveformCanvas.clientWidth; waveformCanvas.height=waveformCanvas.clientHeight; const {width,height}=waveformCanvas; wfCtx.clearRect(0,0,width,height); wfCtx.fillStyle='#ddd'; // usa dados do primeiro canal const data=audioBuffer.getChannelData(0); const samplesPerPixel=Math.floor(data.length/width); for(let x=0; x<width; x++){ // encontra pico para esse pixel let sum=0; for(let i=0;i<samplesPerPixel;i++){ const idx=x*samplesPerPixel+i; sum+=Math.abs(data[idx]); } const peak=sum/samplesPerPixel; const y=peak*height; wfCtx.fillRect(x,(height-y)/2,1,y); } } // marcador de posição “|” function drawPlayhead(){ const {width,height}=waveformCanvas; const ratio=player.currentTime/player.duration||0; const x=ratio*width; // limpa canvas e desenha waveform + playhead drawWaveform(); wfCtx.strokeStyle='#ff0000'; wfCtx.beginPath(); wfCtx.moveTo(x,0); wfCtx.lineTo(x,height); wfCtx.stroke(); } // atualiza playhead em cada frame enquanto toca function startPlayheadLoop(){ cancelAnimationFrame(wfAnimationId); const loop=()=>{ if(!player.paused) drawPlayhead(); wfAnimationId=requestAnimationFrame(loop); }; loop(); } player.addEventListener('play',startPlayheadLoop); player.addEventListener('pause',()=>cancelAnimationFrame(wfAnimationId)); player.addEventListener('seeked',drawPlayhead); player.addEventListener('loadedmetadata',drawWaveform); // ---------- renderização de palavras ---------- function renderList(){ wordListEl.innerHTML=''; words.forEach((w,i)=>{ const span=document.createElement('span'); span.textContent=w.word; span.className='word pending'; span.dataset.index=i; span.addEventListener('click',()=>{ const t=w.recordedTime??w.originalTime; player.currentTime=t; player.play(); }); wordListEl.appendChild(span); }); } function markCurrentWord(){ wordListEl.querySelectorAll('.word').forEach(el=>el.classList.remove('current')); const current=wordListEl.querySelector(`.word[data-index="${currentIdx}"]`); if(current) current.classList.add('current'); } function updateProgress(){ const done=words.filter(w=>w.recordedTime!==undefined).length; progress.textContent=`${done} / ${words.length}`; const hasDone=done>0; undoBtn.disabled=!hasDone; copyBtn.disabled=downloadBtn.disabled=!hasDone; } // ---------- gravação e comandos ---------- startBtn.addEventListener('click',()=>{ currentIdx=0; player.currentTime=0; player.play(); markCurrentWord(); }); function exportSrt(){ return words.filter(w=>w.recordedTime!==undefined) .map(w=>`${formatTime(w.recordedTime)}#${w.word}`) .join('\n'); } function undoLast(){ const last=words.map(w=>w.recordedTime!==undefined).lastIndexOf(true); if(last<0) return; delete words[last].recordedTime; const span=wordListEl.querySelector(`.word[data-index="${last}"]`); span.classList.remove('recorded'); span.classList.add('pending'); currentIdx=last; markCurrentWord(); updateProgress(); output.value=exportSrt(); } document.addEventListener('keydown',e=>{ if(e.code==='Space'){ e.preventDefault(); const now=player.currentTime; if(currentIdx>=words.length) return; const item=words[currentIdx]; item.recordedTime=now; const span=wordListEl.querySelector(`.word[data-index="${currentIdx}"]`); span.classList.remove('pending'); span.classList.add('recorded'); currentIdx=Math.min(currentIdx+1,words.length); markCurrentWord(); updateProgress(); if(currentIdx===words.length){ player.pause(); output.value=exportSrt(); } }else if(e.key.toLowerCase()==='z'){ undoLast(); }else if(e.key==='ArrowLeft'){ player.currentTime=Math.max(0,player.currentTime-2); } }); undoBtn.addEventListener('click',undoLast); backBtn.addEventListener('click',()=>{player.currentTime=Math.max(0,player.currentTime-2);}); copyBtn.addEventListener('click',()=>{navigator.clipboard.writeText(output.value);}); downloadBtn.addEventListener('click',()=>{ const blob=new Blob([output.value],{type:'text/plain'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download='ajustada.srt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }); player.addEventListener('ended',()=>{output.value=exportSrt();}); // ---------- velocidade ---------- function updatePlaybackRate(){ const rate=parseFloat(speedSlider.value); player.playbackRate=rate; speedValue.textContent=`${rate.toFixed(1)}&times;`; } speedSlider.addEventListener('input',updatePlaybackRate); updatePlaybackRate(); // inicial </script> </body> </html>

Como testar

  1. Carregue um arquivo .srt simples com formato min:seg:dec#palavra.
  2. Selecione o áudio correspondente.
  3. Ajuste a velocidade no slider durante a reprodução para ouvir mais lento ou mais rápido.
  4. Observe a forma de onda e o marcador vermelho acompanhando o áudio.
  5. O botão “Voltar 2 s (←)” já está ativo e também responde à tecla de seta ←.

Compartilhar esta Q&A