Spaces:
Sleeping
Sleeping
| """ | |
| Waveform Visualizer Component | |
| Custom Streamlit component using wavesurfer.js | |
| """ | |
| import streamlit as st | |
| import streamlit.components.v1 as components | |
| def waveform_player(audio_url: str, height: int = 128, wavecolor: str = "#4F46E5", progresscolor: str = "#818CF8"): | |
| """ | |
| Render an interactive waveform player using wavesurfer.js | |
| Args: | |
| audio_url: URL or base64 data URL of the audio file | |
| height: Height of the waveform in pixels | |
| wavecolor: Color of the waveform | |
| progresscolor: Color of the progress indicator | |
| Returns: | |
| None (renders component inline) | |
| """ | |
| html_code = f""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script src="https://unpkg.com/wavesurfer.js@7"></script> | |
| <style> | |
| body {{ | |
| margin: 0; | |
| padding: 10px; | |
| background: transparent; | |
| font-family: -apple-system, BlinkMacSystemFont, sans-serif; | |
| }} | |
| #waveform {{ | |
| width: 100%; | |
| background: rgba(0, 0, 0, 0.1); | |
| border-radius: 8px; | |
| overflow: hidden; | |
| }} | |
| .controls {{ | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-top: 10px; | |
| }} | |
| .btn {{ | |
| background: {wavecolor}; | |
| color: white; | |
| border: none; | |
| padding: 8px 16px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| transition: all 0.2s; | |
| }} | |
| .btn:hover {{ | |
| opacity: 0.9; | |
| transform: translateY(-1px); | |
| }} | |
| .time {{ | |
| font-size: 12px; | |
| color: #666; | |
| font-family: monospace; | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <div id="waveform"></div> | |
| <div class="controls"> | |
| <button class="btn" id="playPause">▶️ Play</button> | |
| <button class="btn" id="stop">⏹️ Stop</button> | |
| <span class="time" id="currentTime">0:00</span> | |
| <span class="time">/</span> | |
| <span class="time" id="duration">0:00</span> | |
| </div> | |
| <script> | |
| const wavesurfer = WaveSurfer.create({{ | |
| container: '#waveform', | |
| waveColor: '{wavecolor}', | |
| progressColor: '{progresscolor}', | |
| cursorColor: '#fff', | |
| barWidth: 2, | |
| barRadius: 3, | |
| cursorWidth: 2, | |
| height: {height}, | |
| barGap: 2, | |
| responsive: true, | |
| }}); | |
| wavesurfer.load('{audio_url}'); | |
| const playPauseBtn = document.getElementById('playPause'); | |
| const stopBtn = document.getElementById('stop'); | |
| const currentTimeEl = document.getElementById('currentTime'); | |
| const durationEl = document.getElementById('duration'); | |
| function formatTime(seconds) {{ | |
| const mins = Math.floor(seconds / 60); | |
| const secs = Math.floor(seconds % 60); | |
| return `${{mins}}:${{secs.toString().padStart(2, '0')}}`; | |
| }} | |
| playPauseBtn.addEventListener('click', () => {{ | |
| wavesurfer.playPause(); | |
| }}); | |
| stopBtn.addEventListener('click', () => {{ | |
| wavesurfer.stop(); | |
| }}); | |
| wavesurfer.on('play', () => {{ | |
| playPauseBtn.textContent = '⏸️ Pause'; | |
| }}); | |
| wavesurfer.on('pause', () => {{ | |
| playPauseBtn.textContent = '▶️ Play'; | |
| }}); | |
| wavesurfer.on('ready', () => {{ | |
| durationEl.textContent = formatTime(wavesurfer.getDuration()); | |
| }}); | |
| wavesurfer.on('audioprocess', () => {{ | |
| currentTimeEl.textContent = formatTime(wavesurfer.getCurrentTime()); | |
| }}); | |
| wavesurfer.on('seek', () => {{ | |
| currentTimeEl.textContent = formatTime(wavesurfer.getCurrentTime()); | |
| }}); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| components.html(html_code, height=height + 80) | |
| def waveform_with_regions(audio_url: str, regions: list = None, height: int = 128): | |
| """ | |
| Render waveform with highlighted regions (for word/segment highlighting) | |
| Args: | |
| audio_url: URL of the audio | |
| regions: List of dicts with {start, end, label, color} | |
| height: Waveform height | |
| """ | |
| regions = regions or [] | |
| regions_json = str(regions).replace("'", '"') | |
| html_code = f""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <script src="https://unpkg.com/wavesurfer.js@7"></script> | |
| <script src="https://unpkg.com/wavesurfer.js@7/dist/plugins/regions.min.js"></script> | |
| <style> | |
| body {{ margin: 0; padding: 10px; background: transparent; }} | |
| #waveform {{ width: 100%; border-radius: 8px; }} | |
| </style> | |
| </head> | |
| <body> | |
| <div id="waveform"></div> | |
| <script> | |
| const wavesurfer = WaveSurfer.create({{ | |
| container: '#waveform', | |
| waveColor: '#4F46E5', | |
| progressColor: '#818CF8', | |
| height: {height}, | |
| barWidth: 2, | |
| barRadius: 3, | |
| }}); | |
| const regions = wavesurfer.registerPlugin(WaveSurfer.Regions.create()); | |
| wavesurfer.load('{audio_url}'); | |
| wavesurfer.on('ready', () => {{ | |
| const regionsData = {regions_json}; | |
| regionsData.forEach(r => {{ | |
| regions.addRegion({{ | |
| start: r.start, | |
| end: r.end, | |
| content: r.label || '', | |
| color: r.color || 'rgba(79, 70, 229, 0.3)', | |
| }}); | |
| }}); | |
| }}); | |
| regions.on('region-clicked', (region, e) => {{ | |
| e.stopPropagation(); | |
| region.play(); | |
| }}); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| components.html(html_code, height=height + 20) | |