← back to Beverlyhillsvideos Tiktok
Render Video #1 brand-intro (kinetic typography, 9:16, 20.6s, $0 local)
b84c711399f89ed4c4dcb3c828581621e4b69f5f · 2026-08-05 17:08:22 -0700 · Steve Abrams
Files touched
A scripts/build_video1.py
Diff
commit b84c711399f89ed4c4dcb3c828581621e4b69f5f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 5 17:08:22 2026 -0700
Render Video #1 brand-intro (kinetic typography, 9:16, 20.6s, $0 local)
---
scripts/build_video1.py | 113 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 113 insertions(+)
diff --git a/scripts/build_video1.py b/scripts/build_video1.py
new file mode 100644
index 0000000..8ec9c14
--- /dev/null
+++ b/scripts/build_video1.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+"""Build @BHvideoguide Video #1 (brand intro) — kinetic typography, 9:16, $0 local.
+Frames via PIL (Didot/Helvetica), assembled by ffmpeg with crossfades + Ken Burns + VO."""
+import os, subprocess
+from PIL import Image, ImageDraw, ImageFont
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+FR = os.path.join(ROOT, "assets", "renders", "frames"); os.makedirs(FR, exist_ok=True)
+W, H = 1080, 1920
+IVORY=(247,244,239); INK=(17,17,17); GOLD=(184,151,90); MUTE=(120,116,110)
+DIDOT="/System/Library/Fonts/Supplemental/Didot.ttc"
+HELV="/System/Library/Fonts/Helvetica.ttc"
+def f(path,sz,idx=0): return ImageFont.truetype(path,sz,index=idx)
+
+def center_text(d, txt, font, y, fill, spacing=0):
+ # supports manual letter-spacing; returns width used
+ if spacing==0:
+ b=d.textbbox((0,0),txt,font=font); w=b[2]-b[0]
+ d.text(((W-w)/2, y), txt, font=font, fill=fill); return
+ widths=[d.textbbox((0,0),c,font=font)[2] for c in txt]
+ total=sum(widths)+spacing*(len(txt)-1)
+ x=(W-total)/2
+ for c,wc in zip(txt,widths):
+ d.text((x,y),c,font=font,fill=fill); x+=wc+spacing
+
+def wordmark(d):
+ # bottom-center lower-third brand stamp on every frame
+ fnt=f(HELV,26)
+ center_text(d,"B E V E R L Y H I L L S V I D E O S",fnt,H-90,MUTE,spacing=2)
+
+def hairline(d,y,w=180):
+ d.rectangle([(W-w)//2,y,(W+w)//2,y+3],fill=GOLD)
+
+def frame(name, draw_fn, bg=IVORY):
+ img=Image.new("RGB",(W,H),bg); d=ImageDraw.Draw(img); draw_fn(d); wordmark(d)
+ p=os.path.join(FR,name); img.save(p); return p
+
+# ---- beat frames ----
+def b1(d):
+ center_text(d,"You've seen",f(DIDOT,96),740,INK)
+ center_text(d,"the postcard.",f(DIDOT,96),860,INK)
+def b2(d):
+ center_text(d,"There's a city",f(DIDOT,86),700,INK)
+ center_text(d,"the postcard",f(DIDOT,86),810,INK)
+ center_text(d,"never shows.",f(DIDOT,86),920,GOLD)
+def stamp(word,sub):
+ def _(d):
+ hairline(d,820)
+ center_text(d,word,f(HELV,64,1),880,INK,spacing=6) # idx1 = bold
+ center_text(d,sub,f(HELV,34),980,MUTE,spacing=1)
+ hairline(d,1060)
+ return _
+def b6(d):
+ center_text(d,"Beverly Hills,",f(DIDOT,100),720,INK)
+ center_text(d,"beyond the",f(DIDOT,100),850,INK)
+ center_text(d,"postcard.",f(DIDOT,100),980,GOLD)
+def endcard(d):
+ center_text(d,"BEVERLY HILLS",f(DIDOT,84),640,INK)
+ center_text(d,"VIDEOS",f(DIDOT,84),740,INK)
+ hairline(d,880)
+ center_text(d,"The 90210 edit. Follow.",f(HELV,40),950,INK,spacing=1)
+ center_text(d,"beverlyhillsvideos.com",f(HELV,38,1),1040,GOLD,spacing=1)
+
+frames=[
+ ("01.png",b1,3.6),
+ ("02.png",b2,4.4),
+ ("03.png",stamp("REAL ESTATE","the summit of global luxury"),2.6),
+ ("04.png",stamp("DINING & DEALS","the tables that run the city"),2.6),
+ ("05.png",stamp("THE QUIET CORNERS","what only locals know"),2.6),
+ ("06.png",b6,4.8),
+ ("07.png",endcard,3.6),
+]
+paths=[]
+for name,fn,dur in frames:
+ paths.append((frame(name,fn),dur))
+print("frames:",len(paths))
+
+# ---- ffmpeg assembly: per-clip Ken Burns zoom, then xfade chain ----
+FPS=30; XF=0.6
+tmp=os.path.join(ROOT,"assets","renders","clips"); os.makedirs(tmp,exist_ok=True)
+clips=[]
+for i,(p,dur) in enumerate(paths):
+ out=os.path.join(tmp,f"c{i:02d}.mp4"); n=int(dur*FPS)
+ # gentle zoom in (1.0 -> 1.05): single looped frame, capped at exactly n frames
+ zoom=f"zoompan=z='min(zoom+0.0007,1.05)':d={n}:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s={W}x{H}:fps={FPS}"
+ subprocess.run(["ffmpeg","-y","-loop","1","-i",p,"-vf",zoom,"-frames:v",str(n),
+ "-c:v","libx264","-pix_fmt","yuv420p","-r",str(FPS),out],check=True,
+ stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
+ clips.append((out,dur))
+# build xfade chain
+inputs=[];
+for c,_ in clips: inputs+=["-i",c]
+fc=""; prev="[0:v]"; offset=0.0
+for i in range(1,len(clips)):
+ offset+=clips[i-1][1]-XF
+ lbl=f"[x{i}]"
+ fc+=f"{prev}[{i}:v]xfade=transition=fade:duration={XF}:offset={offset:.3f}{lbl};"
+ prev=lbl
+fc=fc.rstrip(";")
+vid=os.path.join(ROOT,"assets","renders","video1_silent.mp4")
+subprocess.run(["ffmpeg","-y",*inputs,"-filter_complex",fc,"-map",prev,
+ "-c:v","libx264","-pix_fmt","yuv420p",vid],check=True,
+ stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
+# mux VO (pad audio to video length)
+vo=os.path.join(ROOT,"assets","renders","vo1.wav")
+final=os.path.join(ROOT,"assets","renders","BHvideoguide_video1_brand-intro.mp4")
+subprocess.run(["ffmpeg","-y","-i",vid,"-i",vo,
+ "-filter_complex","[1:a]afade=t=in:st=0:d=0.4,afade=t=out:st=19.8:d=1.2,apad[a]",
+ "-map","0:v","-map","[a]","-c:v","copy","-c:a","aac","-b:a","192k","-shortest",final],
+ check=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
+d=subprocess.run(["ffprobe","-v","error","-show_entries","format=duration:stream=width,height",
+ "-of","default=noprint_wrappers=1",final],capture_output=True,text=True).stdout
+print("FINAL:",final); print(d)
← 0ce54c1 Beverly Hills Videos TikTok launch kit (@BHvideoguide): prof
·
back to Beverlyhillsvideos Tiktok
·
yoloforever cycle 1: legal framework, Daniel voice re-render c0aa96f →