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

115
auto_video_cut/config.py Executable file
View File

@@ -0,0 +1,115 @@
"""Konfiguration laden und validieren."""
from __future__ import annotations
import os
from pathlib import Path
from typing import Any
import yaml
MUSIC_EXTENSIONS = {".mp3", ".wav", ".flac", ".aac", ".ogg"}
VIDEO_EXTENSIONS = {".mp4", ".mov", ".avi", ".mkv"}
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg"}
DEFAULTS: dict[str, Any] = {
"resources": {
"folder": "./resources",
},
"music": {
"mode": "random",
"volume_original": 1.0,
"volume_music": 0.3,
},
"videos": {
"intro": None,
"outro": None,
"transitions": False,
},
"images": {
"title_card": None,
"duration": 3,
},
"silence": {
"threshold_db": -40,
"min_duration": 0.5,
},
"scenes": {
"threshold": 27.0,
},
"output": {
"format": "mp4",
"folder": "./output",
},
}
def _deep_merge(base: dict, override: dict) -> dict:
"""Rekursiv Dictionaries zusammenführen."""
result = dict(base)
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = _deep_merge(result[key], value)
else:
result[key] = value
return result
def load_config(config_path: str | Path | None = None) -> dict[str, Any]:
"""YAML-Konfiguration laden und mit Standardwerten zusammenführen."""
config = dict(DEFAULTS)
if config_path is None:
return config
path = Path(config_path)
if not path.exists():
raise FileNotFoundError(f"Konfigurationsdatei nicht gefunden: {path}")
with open(path, encoding="utf-8") as fh:
user_config = yaml.safe_load(fh) or {}
return _deep_merge(config, user_config)
def validate_config(config: dict[str, Any]) -> list[str]:
"""Konfiguration prüfen, Warnungen zurückgeben."""
warnings: list[str] = []
resources_folder = Path(config["resources"]["folder"])
if not resources_folder.exists():
warnings.append(f"Ressourcen-Ordner existiert nicht: {resources_folder}")
else:
music_folder = resources_folder / "music"
if not music_folder.exists():
warnings.append(f"Musik-Ordner existiert nicht: {music_folder}")
else:
music_files = [
f for f in music_folder.iterdir()
if f.suffix.lower() in MUSIC_EXTENSIONS
]
if not music_files:
warnings.append(f"Keine Musikdateien in: {music_folder}")
vol_orig = config["music"]["volume_original"]
vol_music = config["music"]["volume_music"]
if not (0.0 <= vol_orig <= 1.0):
warnings.append(f"volume_original muss zwischen 0.0 und 1.0 liegen (ist: {vol_orig})")
if not (0.0 <= vol_music <= 1.0):
warnings.append(f"volume_music muss zwischen 0.0 und 1.0 liegen (ist: {vol_music})")
return warnings
def get_resources_folder(config: dict[str, Any]) -> Path:
return Path(config["resources"]["folder"])
def get_music_files(config: dict[str, Any]) -> list[Path]:
"""Alle Musikdateien aus dem konfigurierten Ordner zurückgeben."""
music_folder = get_resources_folder(config) / "music"
if not music_folder.exists():
return []
return sorted(
f for f in music_folder.iterdir()
if f.suffix.lower() in MUSIC_EXTENSIONS
)