#!/usr/bin/env python3 """ br-video-create — BlackRoad Animated Video Generator Usage: br-video-create [output.html] br-video-create --interactive br-video-create --example Reads a JSON config defining scenes and generates a complete animated HTML video following BRAND-LOCK design rules. """ import json import sys import os import textwrap from pathlib import Path from datetime import datetime # ── BlackRoad Design Tokens ───────────────────────────────────────────── GRADIENT = "linear-gradient(90deg,#FF6B2B,#FF2255,#CC00AA,#8844FF,#4488FF,#00D4FF)" FONTS_URL = "https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Inter:wght@300;400&display=swap" EXAMPLE_CONFIG = { "title": "My Awesome Video", "aspect": "16:9", "background": "particles", "scenes": [ { "type": "title", "pretitle": "INTRODUCING", "title": "Something Amazing", "subtitle": "Built from scratch.", "duration": 4000 }, { "type": "quote", "text": "The best way to predict the future is to build it.", "attribution": "— ALAN KAY", "duration": 5000 }, { "type": "stats", "heading": "BY THE NUMBERS", "items": [ {"value": "10K", "label": "Users"}, {"value": "99.9%", "label": "Uptime"}, {"value": "50ms", "label": "Latency"}, {"value": "24/7", "label": "Support"} ], "duration": 5000 }, { "type": "bullets", "heading": "Why Us", "items": [ "Zero cloud dependency", "Open source everything", "Built on sovereign hardware" ], "duration": 5000 }, { "type": "terminal", "title": "demo@localhost — zsh", "lines": [ {"type": "input", "text": "curl api.example.com/status"}, {"type": "success", "text": ' {"status": "operational", "uptime": "99.99%"}'}, {"type": "input", "text": "deploy --production"}, {"type": "info", "text": " Deployed to 3 regions in 2.4s"} ], "duration": 7000 }, { "type": "cta", "title": "Get Started", "subtitle": "example.com", "duration": 4000 } ] } # ── Scene Types ────────────────────────────────────────────────────────── def scene_title(s, idx): pretitle = s.get("pretitle", "") title = s.get("title", "Title") subtitle = s.get("subtitle", "") gradient_title = s.get("gradient", True) title_class = ' class="grad"' if gradient_title else '' pretitle_html = f'
{esc(pretitle)}
' if pretitle else '' subtitle_html = f'
{esc(subtitle)}
' if subtitle else '' return f'''
{pretitle_html}
{esc(title)}
{subtitle_html}
''' def scene_subtitle(s, idx): lines = s.get("lines", [s.get("text", "")]) size = s.get("size", 28) html_lines = "
".join(esc(l) for l in lines) return f'''
{html_lines}
''' def scene_bigtext(s, idx): text = s.get("text", "") gradient = s.get("gradient", False) size = s.get("size", 64) cls = ' class="grad"' if gradient else '' return f'''
{esc(text)}
''' def scene_quote(s, idx): text = s.get("text", "") attr = s.get("attribution", "") attr_html = f'
{esc(attr)}
' if attr else '' return f'''
"{esc(text)}"
{attr_html}
''' def scene_stats(s, idx): heading = s.get("heading", "") items = s.get("items", []) heading_html = f'
{esc(heading)}
' if heading else '' cards = [] for i, item in enumerate(items): delay = i * 0.2 cards.append(f'''
{esc(str(item["value"]))}
{esc(item["label"])}
''') return f'''
{heading_html}
{"".join(cards)}
''' def scene_bullets(s, idx): heading = s.get("heading", "") items = s.get("items", []) icon = s.get("icon", "▸") heading_html = f'
{esc(heading)}
' if heading else '' bullets = [] for i, item in enumerate(items): delay = i * 0.3 bullets.append(f'
{esc(icon)} {esc(item)}
') return f'''
{heading_html}
{"".join(bullets)}
''' def scene_cards(s, idx): heading = s.get("heading", "") items = s.get("items", []) heading_html = f'
{esc(heading)}
' if heading else '' cards = [] for i, item in enumerate(items): delay = i * 0.2 icon = item.get("icon", "") name = item.get("name", "") desc = item.get("description", "") status = item.get("status", "") status_cls = "on" if status.lower() in ("online", "active", "ok", "yes") else "off" if status else "" status_html = f'
{esc(status)}
' if status else '' icon_html = f'
{icon}
' if icon else '' cards.append(f'''
{icon_html}
{esc(name)}
{esc(desc)}
{status_html}
''') return f'''
{heading_html}
{"".join(cards)}
''' def scene_timeline(s, idx): heading = s.get("heading", "THE TIMELINE") items = s.get("items", []) heading_html = f'
{esc(heading)}
' entries = [] for i, item in enumerate(items): delay = i * 0.2 date = item.get("date", "") event = item.get("event", "") detail = item.get("detail", "") detail_html = f'
{esc(detail)}
' if detail else '' entries.append(f'''
{esc(date)}
{esc(event)}
{detail_html}
''') return f'''
{heading_html}
{"".join(entries)}
''' def scene_terminal(s, idx): title = s.get("title", "terminal") lines = s.get("lines", []) term_id = f"term{idx}" lines_json = json.dumps(lines) return f'''
{esc(title)}
''' def scene_comparison(s, idx): heading = s.get("heading", "") left_title = s.get("left_title", "Before") right_title = s.get("right_title", "After") left = s.get("left", []) right = s.get("right", []) heading_html = f'
{esc(heading)}
' if heading else '' left_items = "".join(f'
{esc(x)}
' for i, x in enumerate(left)) right_items = "".join(f'
{esc(x)}
' for i, x in enumerate(right)) return f'''
{heading_html}
{esc(left_title)}
{left_items}
{esc(right_title)}
{right_items}
''' def scene_code(s, idx): code = s.get("code", "") lang = s.get("language", "") caption = s.get("caption", "") caption_html = f'
{esc(caption)}
' if caption else '' return f'''
{esc(lang)}
{esc(code)}
{caption_html}
''' def scene_split(s, idx): """Left text, right visual (big number, icon, or gradient text)""" left_lines = s.get("left", []) right_text = s.get("right", "") right_gradient = s.get("right_gradient", True) left_html = "".join(f'
{esc(l)}
' for l in left_lines) right_cls = ' class="grad"' if right_gradient else '' return f'''
{left_html}
{esc(right_text)}
''' def scene_flow(s, idx): """Horizontal pipeline / data flow""" stages = s.get("stages", []) heading = s.get("heading", "") heading_html = f'
{esc(heading)}
' if heading else '' stage_html = [] for i, st in enumerate(stages): delay = i * 0.3 icon = st.get("icon", "") label = st.get("label", "") stage_html.append(f'
{icon}
{esc(label)}
') if i < len(stages) - 1: stage_html.append('
') return f'''
{heading_html}
{"".join(stage_html)}
''' def scene_metrics(s, idx): """Grid of metric tiles""" items = s.get("items", []) heading = s.get("heading", "") heading_html = f'
{esc(heading)}
' if heading else '' tiles = [] for i, item in enumerate(items): delay = i * 0.12 tiles.append(f'''
{esc(str(item.get("value","")))}
{esc(item.get("label",""))}
''') return f'''
{heading_html}
{"".join(tiles)}
''' def scene_image(s, idx): """Full-screen image or centered image with caption""" src = s.get("src", "") caption = s.get("caption", "") full = s.get("fullscreen", False) caption_html = f'
{esc(caption)}
' if caption else '' if full: return f'''
{caption_html}
''' else: return f'''
{caption_html}
''' def scene_logos(s, idx): """Row of logos or brand names""" items = s.get("items", []) heading = s.get("heading", "") heading_html = f'
{esc(heading)}
' if heading else '' logos = [] for i, item in enumerate(items): delay = i * 0.15 if isinstance(item, dict): text = item.get("name", "") icon = item.get("icon", "") logos.append(f'
{icon}
{esc(text)}
') else: logos.append(f'
{esc(str(item))}
') return f'''
{heading_html}
{"".join(logos)}
''' def scene_cta(s, idx): title = s.get("title", "") subtitle = s.get("subtitle", "") gradient_title = s.get("gradient", True) links = s.get("links", []) title_cls = ' class="grad"' if gradient_title else '' subtitle_html = f'
{esc(subtitle)}
' if subtitle else '' links_html = "" if links: link_items = "".join(f'
{esc(l)}
' for l in links) links_html = f'
{link_items}
' return f'''
{esc(title)}
{subtitle_html}
{links_html}
''' def scene_countdown(s, idx): """Animated number with label""" target = s.get("target", 100) label = s.get("label", "") prefix = s.get("prefix", "") suffix = s.get("suffix", "") return f'''
{esc(prefix)}0{esc(suffix)}
{esc(label)}
''' def scene_reveal(s, idx): """Word-by-word reveal of a statement""" words = s.get("text", "").split() gradient_words = s.get("gradient_words", []) word_spans = [] for i, w in enumerate(words): delay = i * 0.15 cls = "reveal-word grad" if w.strip(".,!?") in gradient_words else "reveal-word" word_spans.append(f'{esc(w)}') return f'''
{" ".join(word_spans)}
''' SCENE_RENDERERS = { "title": scene_title, "subtitle": scene_subtitle, "bigtext": scene_bigtext, "quote": scene_quote, "stats": scene_stats, "bullets": scene_bullets, "cards": scene_cards, "timeline": scene_timeline, "terminal": scene_terminal, "comparison": scene_comparison, "code": scene_code, "split": scene_split, "flow": scene_flow, "metrics": scene_metrics, "image": scene_image, "logos": scene_logos, "cta": scene_cta, "countdown": scene_countdown, "reveal": scene_reveal, } def esc(text): """Escape HTML entities (minimal — preserve intentional HTML in content)""" if not text: return "" return str(text).replace("&", "&").replace("<", "<").replace(">", ">") def bg_particles(): return ''' ''' def bg_mesh(): return ''' ''' def bg_grid(): return ''' ''' def bg_none(): return '' BG_RENDERERS = { "particles": bg_particles, "mesh": bg_mesh, "grid": bg_grid, "none": bg_none, "": bg_none, } def generate(config): title = config.get("title", "BlackRoad Video") aspect = config.get("aspect", "16:9") bg_type = config.get("background", "none") cinematic = config.get("cinematic_bars", False) scenes = config.get("scenes", []) if aspect == "9:16": w, h = 1080, 1920 else: w, h = 1920, 1080 # Render scenes scene_htmls = [] durations = [] for i, s in enumerate(scenes): stype = s.get("type", "title") renderer = SCENE_RENDERERS.get(stype) if not renderer: print(f"Warning: Unknown scene type '{stype}', skipping", file=sys.stderr) continue scene_htmls.append(renderer(s, i + 1)) durations.append(s.get("duration", 4000)) # Background bg_html = BG_RENDERERS.get(bg_type, bg_none)() # Cinematic bars cinema_html = "" if cinematic: cinema_html = '
' durations_js = json.dumps(durations) return f''' {esc(title)} {cinema_html} {bg_html} {chr(10).join(scene_htmls)} ''' def print_usage(): print(""" \033[38;5;205m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m \033[38;5;205m br-video-create — Animated Video Generator\033[0m \033[38;5;205m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m \033[38;5;82mUsage:\033[0m br-video-create [output.html] br-video-create --example br-video-create --types \033[38;5;135mScene Types:\033[0m title Hero title with optional pretitle/subtitle subtitle Multi-line subtitle text bigtext Single big statement quote Quoted text with attribution stats Animated stat counters in a row bullets Bullet point list with stagger cards Info cards with icon/name/desc/status timeline Vertical timeline with dates terminal Typing terminal with colored output comparison Side-by-side before/after code Syntax-highlighted code block split Left text, right big number/word flow Horizontal pipeline stages metrics Grid of metric tiles image Image with optional caption logos Row of brand names/icons cta Call-to-action end card countdown Animated counting number reveal Word-by-word text reveal \033[38;5;135mBackgrounds:\033[0m particles, mesh, grid, none \033[38;5;135mAspect Ratios:\033[0m 16:9 (1920x1080), 9:16 (1080x1920) \033[38;5;69mExample:\033[0m br-video-create --example > my-config.json br-video-create my-config.json my-video.html open my-video.html """) def print_types(): print(json.dumps(list(SCENE_RENDERERS.keys()), indent=2)) def main(): args = sys.argv[1:] if not args or args[0] in ("-h", "--help"): print_usage() sys.exit(0) if args[0] == "--example": print(json.dumps(EXAMPLE_CONFIG, indent=2)) sys.exit(0) if args[0] == "--types": print_types() sys.exit(0) # Read config config_path = args[0] if config_path == "-": config = json.load(sys.stdin) else: with open(config_path) as f: config = json.load(f) # Generate html = generate(config) # Output if len(args) > 1: output_path = args[1] os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) with open(output_path, "w") as f: f.write(html) print(f"\033[38;5;82m✓ Generated: {output_path}\033[0m") print(f"\033[38;5;69m Scenes: {len(config.get('scenes',[]))}\033[0m") print(f"\033[38;5;69m Aspect: {config.get('aspect','16:9')}\033[0m") print(f"\033[38;5;69m Background: {config.get('background','none')}\033[0m") else: print(html) if __name__ == "__main__": main()