Upload a portrait and audio clip — we animate the mouth in sync with the audio, overlay a waveform, and add cinematic effects. Download as video. 100% in your browser.
'); } function downloadResult(){ if(!recordedBlob)return; var a=document.createElement('a');a.href=URL.createObjectURL(recordedBlob);a.download='talking-photo-'+Date.now()+'.webm';a.click(); } function snapshotFrame(){ var a=document.createElement('a');a.href=document.getElementById('mainCanvas').toDataURL('image/jpeg',.92);a.download='talking-photo-frame-'+Date.now()+'.jpg';a.click(); }function setProgress(pct){document.getElementById('progressCircle').style.strokeDashoffset=157-(157*pct/100);} function showStatus(msg){var el=document.getElementById('statusBox');el.textContent=msg;el.style.display='block';} function showErr(m){var e=document.getElementById('errorMsg');e.textContent='⚠ '+m;e.style.display='block';} function hideErr(){document.getElementById('errorMsg').style.display='none';} function resetAll(){ photoImg=null;audioFile=null;audioBuffer=null;recordedBlob=null; if(animReq)cancelAnimationFrame(animReq); if(sourceNode)try{sourceNode.stop();}catch(e){} ['photoPrompt','audioPrompt'].forEach(function(id){document.getElementById(id).style.display='block';}); ['photoLoaded','audioLoaded'].forEach(function(id){document.getElementById(id).style.display='none';}); ['photoZone','audioZone'].forEach(function(id){document.getElementById(id).classList.remove('loaded');}); document.getElementById('photoInput').value='';document.getElementById('audioInput').value=''; document.getElementById('createBtn').disabled=true;document.getElementById('downloadBtn').disabled=true; document.getElementById('playBtn').disabled=true;document.getElementById('resultWrap').style.display='none'; document.getElementById('statusBox').style.display='none';hideErr(); }