106 lines
2.8 KiB
Python
Executable File
106 lines
2.8 KiB
Python
Executable File
"""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("\\", "\\\\")
|