#!/usr/bin/env python3 """Generate Discord voice message waveform from an ogg file. Usage: gen_waveform.py Output: JSON with duration_secs and base64-encoded waveform (256 points, 0-255). """ import subprocess import struct import base64 import json import sys def main(): if len(sys.argv) < 2: print("Usage: gen_waveform.py ") sys.exit(1) ogg_file = sys.argv[1] # Get duration result = subprocess.run( ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", ogg_file], capture_output=True, text=True ) duration_secs = float(result.stdout.strip()) # Extract raw PCM (mono, 16-bit, 48kHz) result = subprocess.run( ["ffmpeg", "-i", ogg_file, "-f", "s16le", "-ac", "1", "-ar", "48000", "-"], capture_output=True ) pcm_data = result.stdout # Parse samples num_samples = len(pcm_data) // 2 samples = struct.unpack(f"<{num_samples}h", pcm_data[:num_samples * 2]) # Generate 256-point waveform points = 256 chunk_size = max(1, num_samples // points) waveform = [] for i in range(points): start = i * chunk_size end = min(start + chunk_size, num_samples) if start >= num_samples: waveform.append(0) continue chunk = samples[start:end] avg = sum(abs(s) for s in chunk) / len(chunk) # Map to 0-255 val = min(255, int(avg / 32768 * 255)) waveform.append(val) waveform_b64 = base64.b64encode(bytes(waveform)).decode() print(json.dumps({ "duration_secs": round(duration_secs, 2), "waveform": waveform_b64 })) if __name__ == "__main__": main()