Whisper / utils /jumpcut.py
basyx's picture
Create jumpcut.py
a5e7c90 verified
Raw
History Blame Contribute Delete
5.14 kB
"""
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)