""" jumpcut.py --------------------------------------- Smart Jump Cut Engine (V8) Purpose: - Remove silence and filler pauses - Improve pacing for short-form video - Optimize retention curve - Create TikTok/Reels-style fast cuts Works fully on CPU (FFmpeg-based). No GPU required. """ import subprocess import os # ===================================================== # CONFIG # ===================================================== TEMP_SILENCE_FILE = "silence_detect.txt" OUTPUT_FILE = "jumpcut_output.mp4" # ===================================================== # SILENCE DETECTION # ===================================================== def detect_silence(video_path): """ Uses ffmpeg silencedetect to find pauses. """ cmd = [ "ffmpeg", "-i", video_path, "-af", "silencedetect=noise=-30dB:d=0.4", "-f", "null", "-" ] result = subprocess.run(cmd, stderr=subprocess.PIPE, text=True) return result.stderr # ===================================================== # PARSE SILENCE TIMESTAMPS # ===================================================== def parse_silence(log): """ Extract silence start/end timestamps """ silences = [] start = None for line in log.split("\n"): if "silence_start" in line: try: start = float(line.split("silence_start:")[1].strip()) except: continue if "silence_end" in line and start is not None: try: end = float(line.split("silence_end:")[1].split("|")[0].strip()) silences.append((start, end)) start = None except: continue return silences # ===================================================== # BUILD FILTER (JUMP CUT LOGIC) # ===================================================== def build_filter(silences, duration): """ Converts silence ranges into ffmpeg trim filter """ if not silences: return None segments = [] last_end = 0 for start, end in silences: if start > last_end: segments.append((last_end, start)) last_end = end if last_end < duration: segments.append((last_end, duration)) filters = [] for i, (start, end) in enumerate(segments): filters.append( f"[0:v]trim=start={start}:end={end},setpts=PTS-STARTPTS[v{i}];" f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[a{i}]" ) video_concat = "".join([f"[v{i}]" for i in range(len(segments))]) audio_concat = "".join([f"[a{i}]" for i in range(len(segments))]) filters.append( f"{video_concat}{audio_concat}concat=n={len(segments)}:v=1:a=1[outv][outa]" ) return ";".join(filters) # ===================================================== # CORE ENGINE # ===================================================== def smart_jumpcut(video_path): """ Main jump cut engine """ print("[JUMPCUT] Analyzing video...") # Step 1: detect silence log = detect_silence(video_path) silences = parse_silence(log) print(f"[JUMPCUT] Detected silences: {len(silences)}") # Step 2: get duration probe_cmd = [ "ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", video_path ] duration = float(subprocess.check_output(probe_cmd).decode().strip()) # Step 3: build filter filter_complex = build_filter(silences, duration) if not filter_complex: print("[JUMPCUT] No silences found, returning original") return video_path # Step 4: render output output_path = OUTPUT_FILE cmd = [ "ffmpeg", "-y", "-i", video_path, "-filter_complex", filter_complex, "-map", "[outv]", "-map", "[outa]", "-c:v", "libx264", "-preset", "ultrafast", "-c:a", "aac", output_path ] print("[JUMPCUT] Rendering optimized video...") subprocess.run(cmd, check=True) print("[JUMPCUT] Done:", output_path) return output_path # ===================================================== # SIMPLE FAST MODE (FALLBACK) # ===================================================== def fast_jumpcut(video_path): """ Lightweight fallback: removes only large pauses quickly """ output = "fast_jumpcut.mp4" cmd = [ "ffmpeg", "-y", "-i", video_path, "-af", "silenceremove=start_periods=1:start_threshold=-30dB:stop_periods=-1", "-c:v", "libx264", "-preset", "ultrafast", "-c:a", "aac", output ] subprocess.run(cmd, check=True) return output # ===================================================== # PUBLIC API # ===================================================== def smart_jumpcut_engine(video_path, mode="smart"): """ Entry point used by main.py """ if mode == "fast": return fast_jumpcut(video_path) return smart_jumpcut(video_path)