| """ |
| broll.py |
| --------------------------------------- |
| AI B-Roll Injection System (V8) |
| |
| Purpose: |
| - Detect topics in transcript |
| - Map topics → generic stock B-roll assets |
| - Overlay or replace segments |
| - Improve retention & visual variety |
| |
| Works in CPU-only environments. |
| No external API dependency required. |
| """ |
|
|
| import os |
| import random |
| import subprocess |
|
|
|
|
| |
| |
| |
|
|
| DEFAULT_BROLL = { |
| "money": "assets/broll/money.mp4", |
| "success": "assets/broll/success.mp4", |
| "business": "assets/broll/business.mp4", |
| "phone": "assets/broll/phone.mp4", |
| "tech": "assets/broll/tech.mp4", |
| "people": "assets/broll/people.mp4", |
| "talking": "assets/broll/talking.mp4", |
| "default": "assets/broll/default.mp4", |
| } |
|
|
|
|
| |
| |
| |
|
|
| def detect_topic(text): |
| """ |
| Simple keyword-based topic classifier. |
| Lightweight (no ML dependency). |
| """ |
|
|
| text = text.lower() |
|
|
| if any(w in text for w in ["money", "rich", "income", "profit"]): |
| return "money" |
|
|
| if any(w in text for w in ["business", "startup", "company"]): |
| return "business" |
|
|
| if any(w in text for w in ["phone", "mobile", "iphone", "android"]): |
| return "phone" |
|
|
| if any(w in text for w in ["tech", "ai", "software", "computer"]): |
| return "tech" |
|
|
| if any(w in text for w in ["success", "win", "achieve"]): |
| return "success" |
|
|
| if any(w in text for w in ["people", "person", "man", "woman"]): |
| return "people" |
|
|
| if any(w in text for w in ["talk", "speak", "say"]): |
| return "talking" |
|
|
| return "default" |
|
|
|
|
| |
| |
| |
|
|
| def extract_segments(words, segment_length=8): |
| """ |
| Converts transcript words into grouped segments. |
| """ |
|
|
| segments = [] |
| buffer = [] |
|
|
| for w in words: |
| buffer.append(w) |
|
|
| if len(buffer) >= segment_length: |
| segments.append(buffer) |
| buffer = [] |
|
|
| if buffer: |
| segments.append(buffer) |
|
|
| return segments |
|
|
|
|
| |
| |
| |
|
|
| def match_broll(segment): |
| """ |
| Map transcript segment → B-roll video |
| """ |
|
|
| text = " ".join([w["word"] for w in segment]) |
| topic = detect_topic(text) |
|
|
| return DEFAULT_BROLL.get(topic, DEFAULT_BROLL["default"]) |
|
|
|
|
| |
| |
| |
|
|
| def overlay_broll(base_video, broll_video, output_path, start_time, duration): |
| """ |
| Overlays B-roll using ffmpeg. |
| Lightweight crossfade approach. |
| """ |
|
|
| cmd = [ |
| "ffmpeg", "-y", |
| "-i", base_video, |
| "-i", broll_video, |
| "-filter_complex", |
| f"[1:v]scale=1080:1920,format=rgba[ov];" |
| f"[0:v][ov]overlay=enable='between(t,{start_time},{start_time+duration})'", |
| "-c:v", "libx264", |
| "-preset", "ultrafast", |
| "-c:a", "copy", |
| output_path |
| ] |
|
|
| subprocess.run(cmd, check=True) |
|
|
|
|
| |
| |
| |
|
|
| def insert_broll(video_path, words=None): |
| """ |
| Full B-roll injection pipeline |
| """ |
|
|
| if not words: |
| |
| return video_path |
|
|
| segments = extract_segments(words) |
|
|
| current_video = video_path |
| outputs = [] |
|
|
| for i, segment in enumerate(segments): |
|
|
| broll = match_broll(segment) |
|
|
| output_file = f"broll_output_{i}.mp4" |
|
|
| start_time = segment[0]["start"] |
| duration = segment[-1]["end"] - start_time |
|
|
| try: |
| overlay_broll( |
| current_video, |
| broll, |
| output_file, |
| start_time, |
| duration |
| ) |
|
|
| current_video = output_file |
| outputs.append(output_file) |
|
|
| except Exception as e: |
| print(f"[BROLL ERROR] Segment {i}: {e}") |
| continue |
|
|
| return outputs[-1] if outputs else video_path |
|
|
|
|
| |
| |
| |
|
|
| def smart_broll_engine(words, hook_boost=True): |
| """ |
| Enhanced version: |
| - prioritizes hook segments |
| - increases emotional pacing |
| """ |
|
|
| segments = extract_segments(words) |
|
|
| prioritized = [] |
|
|
| for seg in segments: |
|
|
| text = " ".join([w["word"] for w in seg]).lower() |
|
|
| score = 0 |
|
|
| if any(k in text for k in ["you", "this", "stop", "now"]): |
| score += 2 |
|
|
| if hook_boost and len(seg) < 5: |
| score += 1 |
|
|
| prioritized.append((score, seg)) |
|
|
| prioritized.sort(reverse=True, key=lambda x: x[0]) |
|
|
| final_video = None |
|
|
| for _, seg in prioritized: |
| final_video = insert_broll(final_video or "input.mp4", seg) |
|
|
| return final_video |