Skip to content

Instantly share code, notes, and snippets.

@tiveor
Created March 13, 2026 02:37
Show Gist options
  • Select an option

  • Save tiveor/0300a127852c8b1ae41772983139bfcf to your computer and use it in GitHub Desktop.

Select an option

Save tiveor/0300a127852c8b1ae41772983139bfcf to your computer and use it in GitHub Desktop.
generate.py
#!/usr/bin/env python3
"""
'The Journey of a Prompt'
A creative visualization of how Claude processes and responds to prompts.
Generated entirely with Python + Pillow + FFmpeg.
"""
import math, random, os, struct, wave, subprocess, colorsys, time
from PIL import Image, ImageDraw, ImageFont
random.seed(42)
# === CONFIG ===
W, H = 1080, 1920
FPS = 30
DURATION = 65
TOTAL = FPS * DURATION
DIR = "/Users/alvarotech/dev/random/claude_video"
# === FONTS ===
def font(sz):
for p in ["/System/Library/Fonts/Menlo.ttc", "/System/Library/Fonts/SFNSMono.ttf"]:
if os.path.exists(p):
try:
return ImageFont.truetype(p, sz)
except:
pass
return ImageFont.load_default()
F_SM = font(22)
F_MD = font(34)
F_LG = font(52)
F_XL = font(80)
F_TINY = font(16)
# === COLORS ===
CYAN = (0, 220, 255)
AMBER = (255, 180, 50)
PURPLE = (160, 60, 255)
WHITE = (240, 240, 250)
TEAL = (0, 180, 180)
WARM = (255, 230, 200)
# === HELPERS ===
def lerp(a, b, t):
return a + (b - a) * max(0, min(1, t))
def ease(t):
t = max(0, min(1, t))
return t * t * (3 - 2 * t)
def ease_out(t):
t = max(0, min(1, t))
return 1 - (1 - t) ** 3
def col_a(c, a):
a = max(0, min(1, a))
return tuple(max(0, min(255, int(v * a))) for v in c)
def blend(c1, c2, t):
return tuple(int(lerp(c1[i], c2[i], t)) for i in range(3))
def hsv(h, s, v):
r, g, b = colorsys.hsv_to_rgb(h % 1, s, v)
return (int(r * 255), int(g * 255), int(b * 255))
# === PRE-COMPUTED DATA ===
PROMPT_TEXT = "Hazme algo increíble..."
CODE_LINES = [
"def understand(prompt):",
" tokens = tokenize(prompt)",
" context = build_context(tokens)",
" meaning = extract_intent(context)",
" return meaning",
"",
"def think(understanding):",
" paths = explore_possibilities(understanding)",
" evaluated = [score(p) for p in paths]",
" best = select_optimal(evaluated)",
" return synthesize(best)",
"",
"def create(thought):",
" structure = plan(thought)",
" content = generate(structure)",
" refined = iterate(content)",
" return refined",
"",
"def respond(creation):",
" formatted = present(creation)",
" return formatted",
"",
"# The journey of every prompt",
"prompt = receive()",
"understood = understand(prompt)",
"thought = think(understood)",
"created = create(thought)",
"response = respond(created)",
"deliver(response) # ✨",
]
# Stars
stars = [(random.randint(0, W), random.randint(0, H), random.random() * 2 + 0.5, random.random() * math.tau)
for _ in range(150)]
# Network nodes
random.seed(77)
nodes = []
for i in range(30):
angle = random.random() * math.tau
dist = random.random() * 350 + 80
nodes.append((W // 2 + math.cos(angle) * dist, H // 2 + math.sin(angle) * dist))
edges = []
for i in range(len(nodes)):
for j in range(i + 1, len(nodes)):
dx = nodes[i][0] - nodes[j][0]
dy = nodes[i][1] - nodes[j][1]
if math.sqrt(dx * dx + dy * dy) < 300:
edges.append((i, j))
random.seed(42)
# === PARTICLE SYSTEM ===
MAX_PARTICLES = 300
class Particle:
__slots__ = ['x', 'y', 'vx', 'vy', 'c', 'life', 'ml', 'sz']
def __init__(s, x, y, vx, vy, c, life, sz=2):
s.x, s.y, s.vx, s.vy = x, y, vx, vy
s.c, s.life, s.ml, s.sz = c, life, life, sz
def update(s):
s.x += s.vx
s.y += s.vy
s.vx *= 0.98
s.vy *= 0.98
s.life -= 1
def alpha(s):
return max(0, s.life / s.ml)
particles = []
def emit(x, y, n, color, spread=3, life=40, sz=2):
for _ in range(n):
a = random.random() * math.tau
sp = random.random() * spread
particles.append(Particle(
x, y, math.cos(a) * sp, math.sin(a) * sp,
color, int(life * (0.5 + random.random() * 0.5)), sz
))
# Trim
while len(particles) > MAX_PARTICLES:
particles.pop(0)
def draw_particles(draw):
to_remove = []
for i, p in enumerate(particles):
p.update()
if p.life <= 0:
to_remove.append(i)
continue
a = p.alpha()
c = col_a(p.c, a)
r = p.sz * a
if r > 0.3:
draw.ellipse([p.x - r, p.y - r, p.x + r, p.y + r], fill=c)
for i in reversed(to_remove):
particles.pop(i)
# === PRE-COMPUTE BACKGROUND ===
print("Pre-computing background...")
BG_IMG = Image.new('RGB', (W, H))
_d = ImageDraw.Draw(BG_IMG)
for y in range(H):
t = y / H
_d.line([(0, y), (W, y)], fill=(int(lerp(6, 12, t)), int(lerp(4, 8, t)), int(lerp(16, 32, t))))
del _d
# === DRAW FUNCTIONS ===
def draw_stars(draw, frame):
for sx, sy, sz, phase in stars:
b = 0.3 + 0.7 * (0.5 + 0.5 * math.sin(frame * 0.015 + phase))
c = col_a(WHITE, b * 0.4)
r = sz * b
draw.ellipse([sx - r, sy - r, sx + r, sy + r], fill=c)
def draw_vignette(img):
"""Subtle vignette effect using proper alpha compositing"""
overlay = Image.new('RGBA', (W, H), (0, 0, 0, 0))
od = ImageDraw.Draw(overlay)
# Top and bottom bands
for i in range(50):
a = int(180 * ((50 - i) / 50) ** 2)
r = i * 2
od.rectangle([0, 0, W, r], fill=(0, 0, 0, a))
od.rectangle([0, H - r, W, H], fill=(0, 0, 0, a))
# Gentle side bands (much narrower)
for i in range(25):
a = int(60 * ((25 - i) / 25) ** 2)
r = i * 2
od.rectangle([0, 0, r, H], fill=(0, 0, 0, a))
od.rectangle([W - r, 0, W, H], fill=(0, 0, 0, a))
img.paste(Image.alpha_composite(img.convert('RGBA'), overlay).convert('RGB'))
# === PHASES ===
def phase_void(draw, f, t, img):
"""0-8s: The void. A cursor blinks."""
draw_stars(draw, f)
# Subtle breathing ambient light
breath = 0.5 + 0.5 * math.sin(f * 0.03)
for r in range(80, 0, -2):
a = (1 - r / 80) * 0.03 * breath
draw.ellipse([W // 2 - r, H // 2 - r, W // 2 + r, H // 2 + r], fill=col_a(PURPLE, a))
# Blinking cursor
if t > 0.3:
cursor_a = ease((t - 0.3) / 0.3)
if int(f / 18) % 2 == 0:
cx, cy = W // 2 - 80, H // 2
draw.rectangle([cx, cy - 22, cx + 3, cy + 22], fill=col_a(AMBER, cursor_a))
# Small hint text
if t > 0.7:
a = ease((t - 0.7) / 0.3) * 0.25
draw.text((W // 2, H * 0.65), "esperando tu idea...", fill=col_a(WHITE, a), font=F_SM, anchor="mm")
def phase_prompt(draw, f, t, img):
"""8-18s: The prompt types out character by character."""
draw_stars(draw, f)
chars_shown = int(ease(min(1, t * 1.3)) * len(PROMPT_TEXT))
chars_shown = min(chars_shown, len(PROMPT_TEXT))
text = PROMPT_TEXT[:chars_shown]
if text:
# Get text dimensions
bbox = F_LG.getbbox(text)
tw = bbox[2] - bbox[0]
tx = W // 2
ty = H // 2
# Glow behind text
for r in range(30, 0, -3):
a = (1 - r / 30) * 0.06
draw.rounded_rectangle(
[tx - tw // 2 - r - 15, ty - r - 25, tx + tw // 2 + r + 15, ty + r + 25],
radius=r + 5, fill=col_a(AMBER, a)
)
# Draw text
draw.text((tx, ty), text, fill=AMBER, font=F_LG, anchor="mm")
# Cursor
if int(f / 16) % 2 == 0 and chars_shown < len(PROMPT_TEXT):
cx = tx + tw // 2 + 8
draw.rectangle([cx, ty - 20, cx + 3, ty + 20], fill=AMBER)
# Character particles
if chars_shown < len(PROMPT_TEXT) and f % 2 == 0:
emit(tx + tw // 2, ty, 3, AMBER, spread=2, life=25, sz=1.5)
# Subtle decorative lines on sides
if t > 0.5:
la = ease((t - 0.5) * 2) * 0.15
for i in range(5):
y_off = H // 2 + (i - 2) * 80
w = 30 + i * 10
draw.line([50, y_off, 50 + w, y_off], fill=col_a(AMBER, la * (0.5 + 0.5 * math.sin(f * 0.05 + i))), width=1)
draw.line([W - 50, y_off, W - 50 - w, y_off], fill=col_a(AMBER, la * (0.5 + 0.5 * math.sin(f * 0.05 + i + 1))), width=1)
draw_particles(draw)
def phase_comprehension(draw, f, t, img):
"""18-28s: Text dissolves into understanding."""
draw_stars(draw, f)
# Dissolving text
if t < 0.35:
dissolve = ease(t / 0.35)
full_bbox = F_LG.getbbox(PROMPT_TEXT)
full_tw = full_bbox[2] - full_bbox[0]
base_x = W // 2 - full_tw // 2
accum_w = 0
for i, ch in enumerate(PROMPT_TEXT):
ch_bbox = F_LG.getbbox(ch)
ch_w = ch_bbox[2] - ch_bbox[0] if ch.strip() else 15
cx = base_x + accum_w + ch_w // 2
cy = H // 2
scatter = dissolve * 300
ox = math.sin(i * 1.7 + f * 0.08) * scatter
oy = math.cos(i * 2.3 + f * 0.08) * scatter * 0.6
a = 1 - dissolve
if a > 0.05 and ch.strip():
draw.text((cx + ox, cy + oy), ch, fill=col_a(AMBER, a), font=F_LG, anchor="mm")
if dissolve > 0.2 and f % 4 == i % 4:
emit(cx + ox, cy + oy, 1, AMBER, spread=1, life=15, sz=1)
accum_w += ch_w + 2
# Central comprehension node
if t > 0.2:
nt = ease((t - 0.2) / 0.5)
cx, cy = W // 2, H // 2
# Expanding rings
for ring in range(3):
ring_r = (40 + ring * 60) * nt
ring_a = (1 - ring / 3) * 0.2 * nt
phase_offset = f * 0.02 + ring * 0.5
# Dashed ring
for seg in range(36):
a1 = seg * math.tau / 36 + phase_offset
a2 = a1 + math.tau / 72
x1, y1 = cx + math.cos(a1) * ring_r, cy + math.sin(a1) * ring_r
x2, y2 = cx + math.cos(a2) * ring_r, cy + math.sin(a2) * ring_r
draw.line([x1, y1, x2, y2], fill=col_a(CYAN, ring_a), width=2)
# Central glow
for r in range(int(50 * nt), 0, -2):
a = (1 - r / (50 * nt + 1)) * 0.12 * nt
draw.ellipse([cx - r, cy - r, cx + r, cy + r], fill=col_a(CYAN, a))
# Core
cr = int(10 * nt)
draw.ellipse([cx - cr, cy - cr, cx + cr, cy + cr], fill=col_a(CYAN, nt))
# Radiating connections
if t > 0.45:
ct = ease((t - 0.45) / 0.55)
n_rays = int(ct * 16)
for i in range(n_rays):
angle = i * math.tau / 16 + f * 0.003 + math.sin(f * 0.01 + i) * 0.1
length = (100 + 150 * ct) * (0.8 + 0.2 * math.sin(f * 0.04 + i * 2))
ex = W // 2 + math.cos(angle) * length
ey = H // 2 + math.sin(angle) * length
# Gradient line (draw segments)
steps = 10
for s in range(steps):
st = s / steps
et = (s + 1) / steps
sx_ = lerp(W // 2, ex, st)
sy_ = lerp(H // 2, ey, st)
ex_ = lerp(W // 2, ex, et)
ey_ = lerp(H // 2, ey, et)
a = ct * 0.5 * (1 - st * 0.7)
draw.line([sx_, sy_, ex_, ey_], fill=col_a(CYAN, a), width=1)
# End node
nr = 4 * ct
draw.ellipse([ex - nr, ey - nr, ex + nr, ey + nr], fill=col_a(TEAL, ct * 0.8))
# Label
if 0.3 < t < 0.95:
la = min(1, (t - 0.3) * 5) * min(1, (0.95 - t) * 10)
draw.text((W // 2, H * 0.22), "comprendiendo...", fill=col_a(CYAN, la * 0.5), font=F_MD, anchor="mm")
draw_particles(draw)
def phase_thinking(draw, f, t, img):
"""28-42s: The mind at work."""
draw_stars(draw, f)
# Network: edges with energy
node_a = ease(min(1, t * 3))
for i, j in edges:
x1, y1 = nodes[i]
x2, y2 = nodes[j]
draw.line([x1, y1, x2, y2], fill=col_a(PURPLE, node_a * 0.2), width=1)
# Energy pulse
pulse_t = (f * 0.025 + i * 0.7 + j * 0.3) % 1
px = lerp(x1, x2, pulse_t)
py = lerp(y1, y2, pulse_t)
draw.ellipse([px - 2, py - 2, px + 2, py + 2], fill=col_a(CYAN, node_a * 0.7))
# Nodes
for i, (nx, ny) in enumerate(nodes):
breath = 0.6 + 0.4 * math.sin(f * 0.04 + i * 1.1)
r = 5 * breath * node_a
# Small glow
for gr in range(int(r * 2.5), 0, -3):
a = (1 - gr / (r * 2.5 + 1)) * 0.08 * node_a
draw.ellipse([nx - gr, ny - gr, nx + gr, ny + gr], fill=col_a(CYAN, a))
draw.ellipse([nx - r, ny - r, nx + r, ny + r], fill=col_a(CYAN, node_a * breath))
# Flowing code lines (background texture)
if t > 0.15:
code_t = ease((t - 0.15) / 0.4)
for i, line in enumerate(CODE_LINES):
if not line:
continue
# Lines scroll left slowly
lx = ((f * 0.8 + i * 200) % (W + 600)) - 300
ly = 120 + i * 50
a = 0.08 + 0.06 * math.sin(f * 0.02 + i)
a *= code_t
if line.startswith("def "):
c = col_a(CYAN, a)
elif line.startswith("#"):
c = col_a(PURPLE, a)
else:
c = col_a(TEAL, a)
draw.text((lx, ly), line, fill=c, font=F_TINY)
# Orbiting math symbols
symbols = ["λ", "∑", "∞", "Δ", "Ω", "π", "∫", "≡", "∂", "⊕"]
for i, sym in enumerate(symbols):
angle = f * 0.015 + i * math.tau / len(symbols)
orbit_r = 280 + 60 * math.sin(f * 0.008 + i * 2)
sx = W // 2 + math.cos(angle) * orbit_r
sy = H // 2 + math.sin(angle) * orbit_r * 0.5
a = (0.3 + 0.3 * math.sin(f * 0.04 + i * 1.5)) * node_a
draw.text((sx, sy), sym, fill=col_a(PURPLE, a), font=F_MD, anchor="mm")
# "Thinking" label - appears and fades
if 0.1 < t < 0.92:
la = min(1, (t - 0.1) * 4) * min(1, (0.92 - t) * 6)
# Pulsing alpha
la *= 0.5 + 0.2 * math.sin(f * 0.06)
draw.text((W // 2, H * 0.88), "pensando...", fill=col_a(WHITE, la * 0.5), font=F_MD, anchor="mm")
# Periodic bursts
if f % 15 == 0:
ni = random.randint(0, len(nodes) - 1)
emit(nodes[ni][0], nodes[ni][1], 4, random.choice([CYAN, PURPLE, TEAL]), spread=3, life=30, sz=2)
draw_particles(draw)
def phase_creation(draw, f, t, img):
"""42-52s: Building the response."""
draw_stars(draw, f)
# Code assembling line by line
vis = int(ease(t) * len(CODE_LINES) * 1.3)
vis = min(vis, len(CODE_LINES))
start_y = H * 0.12
lh = 46
for i in range(vis):
line = CODE_LINES[i]
ly = start_y + i * lh
# Slide in animation per line
line_progress = max(0, min(1, (t * len(CODE_LINES) * 1.3 - i) / 3))
lp = ease_out(line_progress)
slide = (1 - lp) * 400
a = lp
# Color based on syntax
if line.startswith("def ") or line.startswith("# "):
c = col_a(CYAN, a * 0.95)
elif "return" in line:
c = col_a(AMBER, a * 0.85)
elif "=" in line:
c = col_a(PURPLE, a * 0.8)
elif line.startswith(" "):
c = col_a(TEAL, a * 0.75)
else:
c = col_a(WHITE, a * 0.6)
if line.strip():
draw.text((130 + slide, ly), line, fill=c, font=F_SM)
# Trailing particle
if 0.3 < line_progress < 0.7 and f % 3 == 0:
emit(130, ly + 10, 1, CYAN, spread=1.5, life=15, sz=1)
# Line numbers
for i in range(vis):
line = CODE_LINES[i]
if not line.strip():
continue
ly = start_y + i * lh
line_progress = max(0, min(1, (t * len(CODE_LINES) * 1.3 - i) / 3))
a = ease_out(line_progress) * 0.25
draw.text((85, ly), f"{i + 1:2d}", fill=col_a(WHITE, a), font=F_TINY)
# Left border line
if t > 0.1:
border_h = min(1, t * 1.5) * vis * lh
border_a = ease(min(1, t * 3)) * 0.3
draw.line([115, start_y - 5, 115, start_y + border_h], fill=col_a(CYAN, border_a), width=2)
# Progress bar at bottom
bar_y = H * 0.90
bar_w = W * 0.6
bar_x = (W - bar_w) / 2
bar_h = 4
draw.rounded_rectangle([bar_x, bar_y, bar_x + bar_w, bar_y + bar_h], radius=2, fill=col_a(WHITE, 0.08))
prog_w = bar_w * ease(t)
if prog_w > 2:
draw.rounded_rectangle([bar_x, bar_y, bar_x + prog_w, bar_y + bar_h], radius=2, fill=col_a(CYAN, 0.7))
# Glow at tip
tip_x = bar_x + prog_w
for r in range(8, 0, -1):
a = (1 - r / 8) * 0.2
draw.ellipse([tip_x - r, bar_y + 2 - r, tip_x + r, bar_y + 2 + r], fill=col_a(CYAN, a))
# Percentage
pct = int(ease(t) * 100)
draw.text((W // 2, bar_y + 30), f"{pct}%", fill=col_a(WHITE, 0.4), font=F_SM, anchor="mm")
# Label
draw.text((W // 2, H * 0.95), "construyendo...", fill=col_a(WHITE, 0.4), font=F_MD, anchor="mm")
draw_particles(draw)
def phase_response(draw, f, t, img):
"""52-65s: The response emerges."""
draw_stars(draw, f)
# Initial flash
if t < 0.06:
flash_a = (1 - t / 0.06) * 0.25
c = col_a(WHITE, flash_a)
draw.rectangle([0, 0, W, H], fill=c)
# Ambient particles
if f % 8 == 0:
x = random.randint(100, W - 100)
y = random.randint(300, H - 300)
emit(x, y, 1, random.choice([CYAN, PURPLE, TEAL]), spread=0.5, life=50, sz=1.5)
# Central poetic text
response_lines = [
("La respuesta emerge", WARM, F_MD),
("del silencio digital,", WARM, F_MD),
("cada palabra tejida", WARM, F_MD),
("con hilos de lógica", WARM, F_MD),
("y creatividad.", WARM, F_MD),
("", None, None),
("No soy solo código.", CYAN, F_MD),
("Soy el puente entre", CYAN, F_MD),
("tu idea", AMBER, F_LG),
("y su forma.", PURPLE, F_LG),
]
if t > 0.04:
text_t = ease(min(1, (t - 0.04) / 0.35))
start_y = H * 0.22
for i, (line, color, fnt) in enumerate(response_lines):
if not line:
continue
line_delay = i * 0.07
lt = max(0, min(1, (text_t - line_delay) / 0.15))
if lt <= 0:
continue
a = ease(lt)
ly = start_y + i * 65
slide_y = (1 - a) * 40
# Subtle glow for emphasized lines
if fnt == F_LG:
for r in range(20, 0, -3):
ga = (1 - r / 20) * 0.04 * a
bbox = fnt.getbbox(line)
tw = bbox[2] - bbox[0]
draw.rounded_rectangle(
[W // 2 - tw // 2 - r - 10, ly + slide_y - r - 15,
W // 2 + tw // 2 + r + 10, ly + slide_y + r + 15],
radius=r, fill=col_a(color, ga)
)
draw.text((W // 2, ly + slide_y), line, fill=col_a(color, a * 0.9), font=fnt, anchor="mm")
# Divider line
if t > 0.5:
div_t = ease(min(1, (t - 0.5) / 0.15))
line_w = int(250 * div_t)
ly = H * 0.73
# Gradient line
for x in range(W // 2 - line_w, W // 2 + line_w):
dist = abs(x - W // 2) / (line_w + 1)
a = (1 - dist) * 0.5 * div_t
draw.point((x, ly), fill=col_a(PURPLE, a))
# Claude signature
if t > 0.55:
sig_t = ease(min(1, (t - 0.55) / 0.15))
sig_y = H * 0.76
# Glow behind name
for r in range(40, 0, -3):
a = (1 - r / 40) * 0.05 * sig_t
draw.ellipse([W // 2 - r * 3, sig_y - r, W // 2 + r * 3, sig_y + r], fill=col_a(CYAN, a))
draw.text((W // 2, sig_y), "Claude", fill=col_a(CYAN, sig_t * 0.95), font=F_XL, anchor="mm")
# Tagline
if t > 0.65:
tag_t = ease(min(1, (t - 0.65) / 0.15))
draw.text((W // 2, H * 0.80), "thinking with you", fill=col_a(WHITE, tag_t * 0.35), font=F_SM, anchor="mm")
# Decorative corner elements
if t > 0.6:
dt = ease(min(1, (t - 0.6) / 0.2))
corner_len = int(60 * dt)
ca = dt * 0.3
# Top-left
draw.line([60, 180, 60 + corner_len, 180], fill=col_a(CYAN, ca), width=1)
draw.line([60, 180, 60, 180 + corner_len], fill=col_a(CYAN, ca), width=1)
# Top-right
draw.line([W - 60, 180, W - 60 - corner_len, 180], fill=col_a(CYAN, ca), width=1)
draw.line([W - 60, 180, W - 60, 180 + corner_len], fill=col_a(CYAN, ca), width=1)
# Bottom-left
draw.line([60, H - 180, 60 + corner_len, H - 180], fill=col_a(PURPLE, ca), width=1)
draw.line([60, H - 180, 60, H - 180 - corner_len], fill=col_a(PURPLE, ca), width=1)
# Bottom-right
draw.line([W - 60, H - 180, W - 60 - corner_len, H - 180], fill=col_a(PURPLE, ca), width=1)
draw.line([W - 60, H - 180, W - 60, H - 180 - corner_len], fill=col_a(PURPLE, ca), width=1)
draw_particles(draw)
# Final fade to black
if t > 0.88:
fade = ease((t - 0.88) / 0.12)
# Draw darkening overlay
c = blend((0, 0, 0), (0, 0, 0), 0) # just black
a = fade * 0.95
# Approximate fade by drawing semi-transparent black strips
strips = 20
for s in range(strips):
sy = s * H // strips
sh = H // strips + 1
draw.rectangle([0, sy, W, sy + sh], fill=col_a((20, 20, 40), a))
# === PHASE TIMELINE ===
PHASES = [
(0, 8, phase_void),
(8, 18, phase_prompt),
(18, 28, phase_comprehension),
(28, 42, phase_thinking),
(42, 52, phase_creation),
(52, 65, phase_response),
]
def render_frame(frame_num):
img = BG_IMG.copy()
draw = ImageDraw.Draw(img)
t_sec = frame_num / FPS
# Find active phase
for start, end, func in PHASES:
if start <= t_sec < end:
t = (t_sec - start) / (end - start)
func(draw, frame_num, t, img)
break
# Vignette
draw_vignette(img)
# Global fade in
if t_sec < 1.5:
fade = t_sec / 1.5
# Darken
a = 1 - fade
for y in range(0, H, 40):
draw.rectangle([0, y, W, y + 40], fill=col_a((6, 4, 16), a))
return img
# === GENERATE AUDIO ===
def generate_audio():
print("Generating ambient audio...")
SR = 44100
n = SR * DURATION
audio_file = f"{DIR}/ambient.wav"
samples = []
for i in range(n):
t = i / SR
prog = t / DURATION
# Base drone
val = 0.12 * math.sin(2 * math.pi * 55 * t)
val += 0.08 * math.sin(2 * math.pi * 82.41 * t)
val += 0.06 * math.sin(2 * math.pi * 110 * t)
# Building harmonics
val += 0.04 * prog * math.sin(2 * math.pi * 220 * t)
val += 0.025 * prog * math.sin(2 * math.pi * 329.63 * t)
# Shimmer
val += 0.015 * math.sin(2 * math.pi * 440 * t) * math.sin(2 * math.pi * 0.4 * t) * prog
# Phase-specific sounds
# Comprehension pulse (18-28s)
if 18 < t < 28:
env = math.sin(math.pi * (t - 18) / 10)
val += 0.03 * env * math.sin(2 * math.pi * 554.37 * t) * math.sin(2 * math.pi * 1.5 * t)
# Thinking complexity (28-42s)
if 28 < t < 42:
env = math.sin(math.pi * (t - 28) / 14)
val += 0.035 * env * math.sin(2 * math.pi * 659.25 * t)
val += 0.02 * env * math.sin(2 * math.pi * 880 * t) * math.sin(2 * math.pi * 0.25 * t)
# Creation build (42-52s)
if 42 < t < 52:
pt = (t - 42) / 10
val += 0.05 * pt * math.sin(2 * math.pi * 164.81 * t)
val += 0.03 * pt * math.sin(2 * math.pi * 196 * t)
# Resolution chord (52-65s)
if t > 52:
pt = min(1, (t - 52) / 4)
fade = max(0, 1 - max(0, t - 60) / 5)
val += 0.05 * pt * fade * math.sin(2 * math.pi * 261.63 * t)
val += 0.04 * pt * fade * math.sin(2 * math.pi * 329.63 * t)
val += 0.035 * pt * fade * math.sin(2 * math.pi * 392 * t)
val += 0.02 * pt * fade * math.sin(2 * math.pi * 523.25 * t)
# Master envelope
if t < 2:
val *= t / 2
if t > DURATION - 3:
val *= max(0, (DURATION - t) / 3)
val = max(-0.9, min(0.9, val))
samples.append(int(val * 32767))
with wave.open(audio_file, 'w') as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(SR)
wf.writeframes(struct.pack(f'<{len(samples)}h', *samples))
print(f"Audio saved: {audio_file}")
return audio_file
# === MAIN ===
def main():
total_start = time.time()
# Generate audio first
audio_file = generate_audio()
# Render video frames piped directly to ffmpeg
print(f"Rendering {TOTAL} frames at {W}x{H} @ {FPS}fps...")
output = f"{DIR}/journey_of_a_prompt.mp4"
cmd = [
'ffmpeg', '-y',
'-f', 'rawvideo', '-pix_fmt', 'rgb24',
'-s', f'{W}x{H}', '-r', str(FPS),
'-i', '-',
'-i', audio_file,
'-c:v', 'libx264', '-preset', 'medium', '-crf', '22',
'-c:a', 'aac', '-b:a', '192k',
'-pix_fmt', 'yuv420p',
'-movflags', '+faststart',
'-shortest',
output
]
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
render_start = time.time()
for f in range(TOTAL):
img = render_frame(f)
proc.stdin.write(img.tobytes())
if f % 150 == 0 and f > 0:
elapsed = time.time() - render_start
fps_rate = f / elapsed
eta = (TOTAL - f) / fps_rate
print(f" Frame {f}/{TOTAL} ({f * 100 // TOTAL}%) - {fps_rate:.1f} render fps - ETA: {eta:.0f}s")
proc.stdin.close()
proc.wait()
if proc.returncode != 0:
err = proc.stderr.read().decode()
print(f"FFmpeg error:\n{err}")
return
total_elapsed = time.time() - total_start
file_size = os.path.getsize(output) / (1024 * 1024)
print(f"\n✓ Video saved: {output}")
print(f" Size: {file_size:.1f} MB")
print(f" Total time: {total_elapsed:.1f}s")
print(f" Duration: {DURATION}s")
print(f" Resolution: {W}x{H}")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment