Initial commit: auto-video-cut project

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christoph K.
2026-04-06 21:51:01 +02:00
commit 267070ad52
15 changed files with 2635 additions and 0 deletions

105
auto_video_cut/text.py Executable file
View File

@@ -0,0 +1,105 @@
"""Text-Einblendungen und Overlays via ffmpeg drawtext."""
from __future__ import annotations
import subprocess
from pathlib import Path
def _run(cmd: list[str]) -> subprocess.CompletedProcess:
return subprocess.run(cmd, capture_output=True, text=True, check=False)
_POSITION_MAP = {
"center": ("(w-text_w)/2", "(h-text_h)/2"),
"top": ("(w-text_w)/2", "50"),
"bottom": ("(w-text_w)/2", "h-text_h-50"),
}
def create_text_clip(
output_path: Path,
content: str,
duration: float = 3.0,
font_size: int = 72,
font_color: str = "white",
background_color: str = "black",
position: str = "center",
width: int = 1920,
height: int = 1080,
) -> Path:
"""Text-Standbild-Clip erzeugen (schwarzer/farbiger Hintergrund)."""
output_path.parent.mkdir(parents=True, exist_ok=True)
x_expr, y_expr = _POSITION_MAP.get(position, _POSITION_MAP["center"])
# Hintergrund-Farbe: "transparent" → schwarzer Hintergrund mit alpha
bg = "black" if background_color == "transparent" else background_color
drawtext = (
f"drawtext=text='{_escape_text(content)}':"
f"fontsize={font_size}:"
f"fontcolor={font_color}:"
f"x={x_expr}:y={y_expr}"
)
cmd = [
"ffmpeg", "-y",
"-f", "lavfi",
"-i", f"color=c={bg}:size={width}x{height}:rate=25:duration={duration}",
"-vf", drawtext,
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
str(output_path),
]
result = _run(cmd)
if result.returncode != 0:
raise RuntimeError(f"ffmpeg drawtext Fehler: {result.stderr}")
return output_path
def add_text_overlay(
input_path: Path,
output_path: Path,
text: str,
position: str = "bottom",
duration: float | None = None,
font_size: int = 48,
font_color: str = "white",
) -> Path:
"""Text-Overlay auf ein laufendes Video legen."""
output_path.parent.mkdir(parents=True, exist_ok=True)
x_expr, y_expr = _POSITION_MAP.get(position, _POSITION_MAP["bottom"])
if duration is not None:
enable = f"enable='between(t,0,{duration})'"
else:
enable = "enable=1"
drawtext = (
f"drawtext=text='{_escape_text(text)}':"
f"fontsize={font_size}:"
f"fontcolor={font_color}:"
f"x={x_expr}:y={y_expr}:"
f"{enable}"
)
cmd = [
"ffmpeg", "-y",
"-i", str(input_path),
"-vf", drawtext,
"-c:a", "copy",
str(output_path),
]
result = _run(cmd)
if result.returncode != 0:
raise RuntimeError(f"ffmpeg overlay Fehler: {result.stderr}")
return output_path
def _escape_text(text: str) -> str:
"""Sonderzeichen für ffmpeg drawtext escapen."""
return text.replace("'", "\\'").replace(":", "\\:").replace("\\", "\\\\")