Initial commit: auto-video-cut project
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
105
auto_video_cut/text.py
Executable file
105
auto_video_cut/text.py
Executable 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("\\", "\\\\")
|
||||
Reference in New Issue
Block a user