lordofgaming
Initial VoiceForge deployment (clean)
673435a
"""
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)