Spaces:
Running
Running
| """ | |
| 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 | |
| # ===================================================== | |
| # STOCK B-ROLL LIBRARY (LOCAL FALLBACK) | |
| # ===================================================== | |
| 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", | |
| } | |
| # ===================================================== | |
| # TOPIC DETECTION | |
| # ===================================================== | |
| 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" | |
| # ===================================================== | |
| # SEGMENT ANALYZER | |
| # ===================================================== | |
| 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 | |
| # ===================================================== | |
| # B-ROLL MATCHING ENGINE | |
| # ===================================================== | |
| 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"]) | |
| # ===================================================== | |
| # B-ROLL INSERTION (FFMPEG OVERLAY STRATEGY) | |
| # ===================================================== | |
| 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) | |
| # ===================================================== | |
| # MAIN PIPELINE | |
| # ===================================================== | |
| def insert_broll(video_path, words=None): | |
| """ | |
| Full B-roll injection pipeline | |
| """ | |
| if not words: | |
| # fallback: return original video | |
| 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 | |
| # ===================================================== | |
| # ADVANCED VERSION (V8 EXTENSION) | |
| # ===================================================== | |
| 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 |