Spaces:
Running
Running
| """ | |
| pacing.py | |
| --------------------------------------- | |
| Retention & Pacing Optimization Engine (V8) | |
| Purpose: | |
| - Adjust video pacing for maximum retention | |
| - Compress slow segments | |
| - Emphasize high-value moments | |
| - Create TikTok / Reels optimized flow | |
| Works in CPU-only environments (FFmpeg-based). | |
| """ | |
| import subprocess | |
| import os | |
| # ===================================================== | |
| # CONFIG | |
| # ===================================================== | |
| OUTPUT_FILE = "pacing_optimized.mp4" | |
| SLOW_THRESHOLD = 1.25 # speed multiplier for slow segments | |
| FAST_THRESHOLD = 1.75 # speed multiplier for filler segments | |
| # ===================================================== | |
| # BASIC SEGMENT ESTIMATION (NO ML DEPENDENCY) | |
| # ===================================================== | |
| def estimate_segment_value(text): | |
| """ | |
| Heuristic scoring system: | |
| determines importance of spoken segment. | |
| """ | |
| text = text.lower() | |
| high_value_keywords = [ | |
| "you", "secret", "important", "stop", | |
| "crazy", "insane", "listen", "this", | |
| "money", "success", "life", "truth" | |
| ] | |
| filler_keywords = [ | |
| "um", "uh", "like", "you know", "so", | |
| "actually", "basically" | |
| ] | |
| score = 1.0 | |
| # boost high value words | |
| for w in high_value_keywords: | |
| if w in text: | |
| score += 0.6 | |
| # penalize filler speech | |
| for w in filler_keywords: | |
| if w in text: | |
| score -= 0.4 | |
| return max(0.5, min(score, 2.0)) | |
| # ===================================================== | |
| # SPEED MAP GENERATOR | |
| # ===================================================== | |
| def build_speed_map(words): | |
| """ | |
| Converts transcript into pacing instructions | |
| """ | |
| segments = [] | |
| buffer = [] | |
| for w in words: | |
| buffer.append(w) | |
| # group into micro segments | |
| if len(buffer) >= 6: | |
| segments.append(buffer) | |
| buffer = [] | |
| if buffer: | |
| segments.append(buffer) | |
| speed_map = [] | |
| for seg in segments: | |
| text = " ".join([w["word"] for w in seg]) | |
| score = estimate_segment_value(text) | |
| start = seg[0]["start"] | |
| end = seg[-1]["end"] | |
| # decide speed | |
| if score > 1.4: | |
| speed = 1.0 # keep normal (important content) | |
| elif score > 1.0: | |
| speed = 1.15 # slight compression | |
| else: | |
| speed = FAST_THRESHOLD # aggressive speed-up | |
| speed_map.append({ | |
| "start": start, | |
| "end": end, | |
| "speed": speed | |
| }) | |
| return speed_map | |
| # ===================================================== | |
| # FFMEG FILTER BUILDER | |
| # ===================================================== | |
| def build_filter(speed_map): | |
| """ | |
| Creates FFmpeg atempo + setpts filter chain | |
| """ | |
| filters = [] | |
| for i, seg in enumerate(speed_map): | |
| start = seg["start"] | |
| end = seg["end"] | |
| speed = seg["speed"] | |
| # video speed | |
| filters.append( | |
| f"[0:v]trim=start={start}:end={end},setpts=PTS/{speed}[v{i}]" | |
| ) | |
| # audio speed | |
| filters.append( | |
| f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS," | |
| f"atempo={speed}[a{i}]" | |
| ) | |
| v_streams = "".join([f"[v{i}]" for i in range(len(speed_map))]) | |
| a_streams = "".join([f"[a{i}]" for i in range(len(speed_map))]) | |
| filters.append( | |
| f"{v_streams}{a_streams}concat=n={len(speed_map)}:v=1:a=1[outv][outa]" | |
| ) | |
| return ";".join(filters) | |
| # ===================================================== | |
| # MAIN ENGINE | |
| # ===================================================== | |
| def optimize_pacing(video_path, words=None): | |
| """ | |
| Main entry point for V8 pacing system | |
| """ | |
| print("[PACING] Starting optimization...") | |
| if not words: | |
| print("[PACING] No transcript provided — returning original video") | |
| return video_path | |
| # Step 1: build speed map | |
| speed_map = build_speed_map(words) | |
| print(f"[PACING] Segments: {len(speed_map)}") | |
| # Step 2: build ffmpeg filter | |
| filter_complex = build_filter(speed_map) | |
| output_path = OUTPUT_FILE | |
| # Step 3: render optimized video | |
| cmd = [ | |
| "ffmpeg", "-y", | |
| "-i", video_path, | |
| "-filter_complex", filter_complex, | |
| "-map", "[outv]", | |
| "-map", "[outa]", | |
| "-c:v", "libx264", | |
| "-preset", "ultrafast", | |
| "-c:a", "aac", | |
| output_path | |
| ] | |
| subprocess.run(cmd, check=True) | |
| print("[PACING] Done:", output_path) | |
| return output_path | |
| # ===================================================== | |
| # LIGHTWEIGHT MODE (FAST FALLBACK) | |
| # ===================================================== | |
| def fast_pacing(video_path): | |
| """ | |
| Simple fallback: global speed-up only | |
| """ | |
| output = "fast_pacing.mp4" | |
| cmd = [ | |
| "ffmpeg", "-y", | |
| "-i", video_path, | |
| "-filter_complex", | |
| "[0:v]setpts=0.92*PTS[v];[0:a]atempo=1.08[a]", | |
| "-map", "[v]", | |
| "-map", "[a]", | |
| "-c:v", "libx264", | |
| "-preset", "ultrafast", | |
| "-c:a", "aac", | |
| output | |
| ] | |
| subprocess.run(cmd, check=True) | |
| return output | |
| # ===================================================== | |
| # PUBLIC API | |
| # ===================================================== | |
| def pacing_engine(video_path, words=None, mode="smart"): | |
| """ | |
| Entry point used by main.py | |
| """ | |
| if mode == "fast": | |
| return fast_pacing(video_path) | |
| return optimize_pacing(video_path, words) |