Skip to content

Instantly share code, notes, and snippets.

@twobob
Created October 2, 2025 22:35
Show Gist options
  • Save twobob/077531cecbb77aa44fe97cec23628f5d to your computer and use it in GitHub Desktop.
Save twobob/077531cecbb77aa44fe97cec23628f5d to your computer and use it in GitHub Desktop.
reads a WAV audio file, generates a radial polar visualization where concentric rings pulse and modulate based on the audio waveform amplitude, renders each frame as an image, and combines them into an MP4 video with the original audio synced.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import wave
import contextlib
import cv2
import os
import shutil
from moviepy.editor import AudioFileClip, VideoFileClip
# --- Configuration ---
# Input WAV and output MP4 paths
WAV_PATH = "garbage.wav"
OUTPUT_MP4 = "garbage_VISUAL.mp4"
# Visualization parameters
FPS = 30
FIG_SIZE = (10, 10)
DPI = 120
NUM_LOOPS = 4 # Number of concentric visualizer loops
# Aesthetics
BG_COLOR = '#0B0014'
COLORMAP = cm.magma # Colormap for dynamic colors (e.g., magma, plasma, inferno)
PARTICLE_COLOR = '#FFFFFF'
TITLE_TEXT = 'GARBAGE'
TITLE_COLOR = (1.0, 1.0, 1.0, 0.08)
# --- Setup Directories ---
FRAMES_DIR = "temp_visualization_frames"
if os.path.exists(FRAMES_DIR):
shutil.rmtree(FRAMES_DIR)
os.makedirs(FRAMES_DIR, exist_ok=True)
# --- Audio Processing ---
try:
with contextlib.closing(wave.open(WAV_PATH, 'r')) as wf:
num_channels = wf.getnchannels()
sampwidth = wf.getsampwidth()
framerate = wf.getframerate()
num_frames = wf.getnframes()
audio_data = wf.readframes(num_frames)
dtype_map = {1: np.int8, 2: np.int16, 4: np.int32}
if sampwidth not in dtype_map:
raise ValueError("Unsupported sample width")
audio_np = np.frombuffer(audio_data, dtype=dtype_map[sampwidth])
if num_channels == 2:
audio_np = audio_np.reshape(-1, 2).mean(axis=1)
print("Audio file loaded successfully.")
print(f"Duration: {num_frames / float(framerate):.2f}s, Framerate: {framerate}Hz")
except FileNotFoundError:
print(f"Error: The file '{WAV_PATH}' was not found.")
exit()
except Exception as e:
print(f"An error occurred while reading the audio file: {e}")
exit()
# --- Visualization Frame Generation ---
samples_per_frame = framerate // FPS
num_frames_to_generate = int(num_frames / samples_per_frame)
max_amplitude = np.max(np.abs(audio_np)) if len(audio_np) > 0 else 1.0
print(f"Generating {num_frames_to_generate} frames...")
for i in range(num_frames_to_generate):
start = i * samples_per_frame
end = start + samples_per_frame
frame_audio = audio_np[start:end]
if frame_audio.size == 0:
continue
# --- Create the Radial Visualization ---
fig = plt.figure(figsize=FIG_SIZE, facecolor=BG_COLOR)
ax = plt.subplot(111, polar=True, facecolor=BG_COLOR)
fig.text(0.5, 0.5, TITLE_TEXT, fontsize=120, color=TITLE_COLOR,
ha='center', va='center', weight='bold')
# Calculate RMS for intensity and get the dynamic color for this frame
rms = np.sqrt(np.mean(np.square(frame_audio.astype(np.float64))))
normalized_rms = rms / max_amplitude if max_amplitude > 0 else 0
if not np.isfinite(normalized_rms):
normalized_rms = 0
# The color is determined by the loudness of the frame
dynamic_color = COLORMAP(normalized_rms)
# A slightly brighter color for the main line
glow_color = COLORMAP(min(normalized_rms * 1.2, 1.0))
num_samples = len(frame_audio)
theta = np.linspace(0, 2 * np.pi, num_samples)
# --- Add Particle Effects ---
num_particles = int(150 * normalized_rms)
particle_thetas = np.random.uniform(0, 2 * np.pi, num_particles)
particle_radii = np.random.uniform(0, 1.5, num_particles) * (1 + normalized_rms)
particle_sizes = (np.random.rand(num_particles) * 30) * (0.5 + normalized_rms)
ax.scatter(particle_thetas, particle_radii, s=particle_sizes, color=PARTICLE_COLOR, alpha=0.4 * normalized_rms, zorder=2)
# --- Plot Multiple Concentric Loops ---
for j in range(NUM_LOOPS):
loop_base_radius = 0.4 * (j + 1)
# Modulate radius based on audio waveform
radius_modulation = (np.abs(frame_audio) / max_amplitude) * 0.2 * (1 + normalized_rms) if max_amplitude > 0 else np.zeros_like(frame_audio)
radii = loop_base_radius + radius_modulation
# Ensure smooth wrap-around
loop_theta = np.append(theta, theta[0])
loop_radii = np.append(radii, radii[0])
# Plot glow effect - increased alpha for more intensity
ax.plot(loop_theta, loop_radii, color=glow_color, linewidth=5 + j*2, alpha=0.7 * (0.3 + normalized_rms), zorder=3)
# Plot main line - increased linewidth and always visible
ax.plot(loop_theta, loop_radii, color=dynamic_color, linewidth=2.5, alpha=0.9, zorder=4)
# --- Final Touches on the Plot ---
ax.set_ylim(0, 2.5) # Increased limit for multiple loops
ax.grid(False)
ax.set_xticks([])
ax.set_yticks([])
ax.spines['polar'].set_visible(False)
plt.tight_layout()
frame_path = os.path.join(FRAMES_DIR, f"frame_{i:05d}.png")
plt.savefig(frame_path, dpi=DPI, facecolor=BG_COLOR)
plt.close(fig)
if (i + 1) % 10 == 0:
print(f" ... {i+1}/{num_frames_to_generate} frames rendered")
# --- Video Compilation ---
print("\nAll frames generated. Compiling video...")
first_frame_path = os.path.join(FRAMES_DIR, "frame_00000.png")
if not os.path.exists(first_frame_path):
print("Error: No frames were generated. Cannot create video.")
exit()
frame_example = cv2.imread(first_frame_path)
h, w, _ = frame_example.shape
temp_video_path = os.path.join(FRAMES_DIR, "visual.avi")
# Use MJPEG codec which is more compatible
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
video = cv2.VideoWriter(temp_video_path, fourcc, FPS, (w, h))
for i in range(num_frames_to_generate):
frame_path = os.path.join(FRAMES_DIR, f"frame_{i:05d}.png")
if os.path.exists(frame_path):
frame = cv2.imread(frame_path)
video.write(frame)
video.release()
# --- Combine Video and Audio using ffmpeg-python ---
print("Combining video and audio...")
try:
import ffmpeg
# Use ffmpeg-python to combine video and audio
video_stream = ffmpeg.input(temp_video_path)
audio_stream = ffmpeg.input(WAV_PATH)
output = ffmpeg.output(
video_stream,
audio_stream,
OUTPUT_MP4,
vcodec='libx264',
acodec='aac',
shortest=None,
**{'b:v': '5000k'} # Video bitrate for quality
)
# Overwrite output file if it exists
output = ffmpeg.overwrite_output(output)
# Run the ffmpeg command
ffmpeg.run(output, capture_stdout=True, capture_stderr=True)
print(f"\nSuccessfully created video: {OUTPUT_MP4}")
except ImportError:
print("\nError: ffmpeg-python library not found.")
print("Please install it with: pip install ffmpeg-python")
print("You can find the silent video at:", temp_video_path)
except Exception as e:
print(f"\nAn error occurred during the final video compilation: {e}")
import traceback
traceback.print_exc()
print("You can find the silent video at:", temp_video_path)
finally:
print("Cleaning up temporary files...")
if os.path.exists(FRAMES_DIR):
shutil.rmtree(FRAMES_DIR)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment