Video Schneiden: Automatisierter Schnitt mit Python & FFmpeg
Stille erkennen, Szenen finden, automatisch schneiden — Batch-Verarbeitung inklusive
Python
FFmpeg
OpenCV
🚀 Ihre Videos. Automatisch geschnitten. In Minuten statt Stunden.
💰 Warum automatischer Videoschnitt?
Manueller Videoschnitt ist zeitaufwendig: 1 Stunde Rohmaterial = 3–5 Stunden Schnittarbeit. Automatisierte Erkennung von stillen Passagen und Szenenwechseln reduziert das auf Minuten.
Stille-Erkennung
Erkennt automatisch stille Passagen und schneidet sie heraus — perfekt für Podcasts und Präsentationen.
Szenen-Erkennung
Findet harte Schnitte und Szenenwechsel per Bildanalyse — teilt lange Videos automatisch in Kapitel.
Format-Konvertierung
Schneidet UND konvertiert in einem Schritt — MP4, WebM, GIF, mit einstellbaren Codecs und Bitraten.
Batch-Verarbeitung
Ganze Ordner auf einmal verarbeiten — gleiche Schnittregeln auf hunderte Videos anwenden.
⚙️ So funktioniert's
📊
1. Analysieren
Video einlesen, Audiospur analysieren, stille Passagen und laute Szenenwechsel identifizieren.
✂️
2. Schnittpunkte setzen
Automatisch Timecodes für Schnitte berechnen — mit konfigurierbaren Schwellwerten.
⚙️
3. FFmpeg schneidet
Präziser Schnitt ohne Re-Encoding wo möglich — schnell und qualitätserhaltend.
📁
4. Ausgabe
Geschnittene Clips im Zielordner — optional mit Konvertierung in verschiedene Formate.
💻 Technische Umsetzung
🐍 Video-Schneide-Engine
import subprocess
import json
import os
from pathlib import Path
class VideoCutter:
def __init__(self, silence_threshold: float = -40.0,
min_silence_duration: float = 0.8):
self.silence_threshold = silence_threshold # dB
self.min_silence_duration = min_silence_duration # Sekunden
def detect_silence(self, video_path: str) -> list:
"""Erkennt stille Passagen im Video"""
cmd = [
'ffmpeg', '-i', video_path,
'-af', f'silencedetect=n={self.silence_threshold}dB:'
f'd={self.min_silence_duration}',
'-f', 'null', '-'
]
result = subprocess.run(cmd, capture_output=True, text=True)
# Parse die Ausgabe von silencedetect
silences = []
for line in result.stderr.split('\n'):
if 'silence_start' in line:
start = float(line.split('silence_start: ')[1].split()[0])
silences.append({'start': start})
elif 'silence_end' in line and silences:
end = float(line.split('silence_end: ')[1].split()[0])
dur = float(line.split('silence_duration: ')[1].split()[0])
silences[-1].update({'end': end, 'duration': dur})
return silences
def cut_segments(self, video_path: str, segments: list,
output_dir: str, prefix: str = 'clip') -> list:
"""Schneidet Video in Segmente basierend auf Timecodes"""
output_files = []
for i, segment in enumerate(segments):
start = segment['start']
end = segment['end']
duration = end - start
output_name = f"{prefix}_{i+1:03d}.mp4"
output_path = os.path.join(output_dir, output_name)
# FFmpeg: schneller Schnitt (copy codec wo möglich)
cmd = [
'ffmpeg', '-y',
'-ss', str(start),
'-i', video_path,
'-t', str(duration),
'-c', 'copy', # Ohne Re-Encoding
'-avoid_negative_ts', 'make_zero',
output_path
]
subprocess.run(cmd, capture_output=True, check=True)
output_files.append({
'file': output_path,
'start': start,
'end': end,
'duration': duration
})
print(f"✓ Clip {i+1}: {start:.1f}s - {end:.1f}s → {output_name}")
return output_files
def remove_silence(self, video_path: str, output_path: str) -> str:
"""Entfernt alle stillen Passagen aus einem Video"""
silences = self.detect_silence(video_path)
if not silences:
print("Keine stillen Passagen gefunden.")
return video_path
# Berechne aktive Segmente (zwischen stillen Passagen)
video_duration = self._get_duration(video_path)
active_segments = []
last_end = 0
for silence in silences:
if silence['start'] > last_end:
active_segments.append({
'start': last_end,
'end': silence['start']
})
last_end = silence.get('end', silence['start'])
if last_end < video_duration:
active_segments.append({
'start': last_end,
'end': video_duration
})
# Erstelle Filtergraph für Konkatenation
return self.cut_segments(video_path, active_segments,
os.path.dirname(output_path))
def _get_duration(self, video_path: str) -> float:
"""Ermittelt Videodauer via ffprobe"""
cmd = [
'ffprobe', '-v', 'error',
'-show_entries', 'format=duration',
'-of', 'json', video_path
]
result = subprocess.run(cmd, capture_output=True, text=True)
data = json.loads(result.stdout)
return float(data['format']['duration'])
# Nutzung
cutter = VideoCutter(silence_threshold=-35, min_silence_duration=1.0)
# Stille Passagen finden
silences = cutter.detect_silence('meeting.mp4')
print(f"Gefunden: {len(silences)} stille Passagen")
# Stille entfernen
cutter.remove_silence('meeting.mp4', 'meeting_clean.mp4')
🎬 Szenenerkennung mit OpenCV (optional)
import cv2
import numpy as np
def detect_scene_changes(video_path: str, threshold: float = 30.0) -> list:
"""Erkennt Szenenwechsel durch Frame-Differenz"""
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
scene_changes = []
prev_frame = None
frame_idx = 0
while True:
ret, frame = cap.read()
if not ret:
break
if prev_frame is not None:
# Berechne Frame-Differenz
diff = cv2.absdiff(frame, prev_frame)
mean_diff = np.mean(diff)
if mean_diff > threshold:
timestamp = frame_idx / fps
scene_changes.append({
'frame': frame_idx,
'timestamp': timestamp,
'difference': mean_diff
})
prev_frame = frame.copy()
frame_idx += 1
cap.release()
return scene_changes
⚡ In 2–3 Tagen zum fertigen Video-Schneide-Tool.
🚀 Gebaut mit Vibecoding
👴 Klassische Entwicklung
- 📋 1–2 Wochen FFmpeg lernen
- 💻 2–3 Wochen Implementierung
- 🧪 1 Woche Testing mit verschiedenen Formaten
- ⏱️ Gesamt: 4–6 Wochen
🤖 Vibecoding-Ansatz
- 🗣️ 0.5 Tage Prompt
- ⚡ 1–2 Tage Code-Generierung
- 🔧 0.5 Tage Test & Refinement
- ⏱️ Gesamt: 2–3 Tage
💻 Code-Einblicke
Ein Blick unter die Haube — so erkennt der Cutter automatisch Musik-Segmente:
🏗️ Architektur
video_music_cutter.py
Hauptmodul: Audio-Extraktion, Musiksegment-Erkennung, Video-Schnitt mit MoviePy.
batch_process.py
Batch-Verarbeitung mehrerer Videos mit konfigurierbaren Erkennungsparametern.
download_youtube.py
YouTube-Downloader mit yt-dlp + Cookie-Support für altersbeschränkte Videos.
🎵 Musiksegment-Erkennung mit Audio-Features
import numpy as np
import librosa
from pydub import AudioSegment
from pydub.silence import detect_nonsilent
class MusicSegmentDetector:
def __init__(self, min_music_duration=10, min_silence_len=1000,
silence_thresh=-40, spectral_threshold=0.6):
self.min_music_duration = min_music_duration
self.min_silence_len = min_silence_len
self.silence_thresh = silence_thresh
self.spectral_threshold = spectral_threshold
def analyze_audio_features(self, audio_path):
y, sr = librosa.load(audio_path, sr=22050)
spectral_centroids = librosa.feature.spectral_centroid(y=y, sr=sr)[0]
zcr = librosa.feature.zero_crossing_rate(y)[0]
chroma = librosa.feature.chroma_stft(y=y, sr=sr)
chroma_mean = np.mean(chroma, axis=0)
spectral_rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)[0]
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
return {
'spectral_centroids': spectral_centroids,
'zcr': zcr, 'chroma_mean': chroma_mean,
'spectral_rolloff': spectral_rolloff,
'mfcc': mfcc, 'sr': sr, 'y': y
}
🧠 Musik vs. Sprache: Der Klassifikator
def _calculate_music_score(self, features, start_sec, end_sec):
time_frames = features['time_frames']
start_idx = np.searchsorted(time_frames, start_sec)
end_idx = np.searchsorted(time_frames, end_sec)
# Features für das Segment extrahieren
segment_zcr = features['zcr'][start_idx:end_idx]
segment_spectral = features['spectral_centroids'][start_idx:end_idx]
segment_chroma = features['chroma_mean'][start_idx:end_idx]
# Musik hat: niedrigere ZCR, höhere spektrale Varianz, stärkere Chroma
zcr_score = 1.0 - min(np.mean(segment_zcr), 0.3) / 0.3
spectral_var = np.std(segment_spectral)
spectral_score = min(spectral_var / 1000.0, 1.0)
chroma_score = min(np.mean(segment_chroma), 1.0)
# Gewichteter Score
return (zcr_score * 0.3 + spectral_score * 0.3 + chroma_score * 0.4)
def detect_music_segments(self, audio_path):
audio = AudioSegment.from_file(audio_path)
nonsilent_ranges = detect_nonsilent(
audio, min_silence_len=self.min_silence_len,
silence_thresh=self.silence_thresh)
features = self.analyze_audio_features(audio_path)
music_segments = []
for start_ms, end_ms in nonsilent_ranges:
start_sec = start_ms / 1000.0
end_sec = end_ms / 1000.0
if end_sec - start_sec >= self.min_music_duration:
music_score = self._calculate_music_score(
features, start_sec, end_sec)
if music_score > self.spectral_threshold:
music_segments.append((start_sec, end_sec))
return self._merge_close_segments(music_segments, gap_threshold=2.0)
✂️ Video-Schnitt mit MoviePy
from moviepy.editor import VideoFileClip, concatenate_videoclips
import tempfile
def cut_video_segments(video_path, segments, output_path):
video = VideoFileClip(video_path)
clips = []
for start, end in segments:
clip = video.subclip(start, end)
clips.append(clip)
final_clip = concatenate_videoclips(clips)
final_clip.write_videofile(
output_path, codec='libx264', audio_codec='aac',
temp_audiofile=tempfile.mktemp(suffix='.m4a'),
remove_temp=True)
video.close(); final_clip.close()
for clip in clips:
clip.close()
return True
📦 Batch-Verarbeitung
import glob
from video_music_cutter import process_video
def batch_process_videos(pattern="*.mp4", output_suffix="_music_only",
min_music_duration=10, silence_thresh=-40,
spectral_threshold=0.6):
video_files = glob.glob(pattern)
video_files = [f for f in video_files if output_suffix not in f]
successful, failed = 0, 0
for video_file in video_files:
base, ext = os.path.splitext(video_file)
output_file = f"{base}{output_suffix}{ext}"
try:
if process_video(video_file, output_file,
min_music_duration=min_music_duration,
silence_thresh=silence_thresh,
spectral_threshold=spectral_threshold):
successful += 1
else:
failed += 1
except Exception as e:
print(f"Fehler: {e}")
failed += 1
print(f"Erfolgreich: {successful}, Fehlgeschlagen: {failed}")
Automatischer Videoschnitt — für Ihr Projekt?
Podcast-Bereinigung, Meeting-Cuts oder Content-Repurposing — ich baue die passende Pipeline. Jetzt anfragen.