Initial commit: auto-video-cut project
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
116
auto_video_cut/audio.py
Executable file
116
auto_video_cut/audio.py
Executable file
@@ -0,0 +1,116 @@
|
||||
"""Hintergrundmusik-Mixing und Audio-Logik."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from .config import MUSIC_EXTENSIONS, get_music_files
|
||||
|
||||
|
||||
def _run(cmd: list[str]) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(cmd, capture_output=True, text=True, check=False)
|
||||
|
||||
|
||||
def pick_music_file(
|
||||
music_files: list[Path],
|
||||
mode: str = "random",
|
||||
) -> Path:
|
||||
"""Musikdatei nach Modus auswählen."""
|
||||
if not music_files:
|
||||
raise FileNotFoundError("Keine Musikdateien gefunden.")
|
||||
|
||||
if mode == "random":
|
||||
return random.choice(music_files)
|
||||
elif mode in ("alphabetical", "loop"):
|
||||
return sorted(music_files)[0]
|
||||
else:
|
||||
raise ValueError(f"Unbekannter Musik-Modus: {mode}")
|
||||
|
||||
|
||||
def mix_music(
|
||||
video_path: Path,
|
||||
output_path: Path,
|
||||
music_file: Path,
|
||||
volume_original: float = 1.0,
|
||||
volume_music: float = 0.3,
|
||||
) -> Path:
|
||||
"""Hintergrundmusik in Video mixen."""
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Prüfen ob Video Audio-Stream hat
|
||||
probe_cmd = [
|
||||
"ffprobe", "-v", "error",
|
||||
"-select_streams", "a",
|
||||
"-show_entries", "stream=codec_type",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
str(video_path),
|
||||
]
|
||||
probe = _run(probe_cmd)
|
||||
has_audio = bool(probe.stdout.strip())
|
||||
|
||||
if has_audio:
|
||||
filter_complex = (
|
||||
f"[0:a]volume={volume_original}[v1];"
|
||||
f"[1:a]volume={volume_music}[v2];"
|
||||
f"[v1][v2]amix=inputs=2:duration=first[a]"
|
||||
)
|
||||
cmd = [
|
||||
"ffmpeg", "-y",
|
||||
"-i", str(video_path),
|
||||
"-stream_loop", "-1",
|
||||
"-i", str(music_file),
|
||||
"-filter_complex", filter_complex,
|
||||
"-c:v", "copy",
|
||||
"-c:a", "aac",
|
||||
"-map", "0:v:0",
|
||||
"-map", "[a]",
|
||||
"-shortest",
|
||||
str(output_path),
|
||||
]
|
||||
else:
|
||||
# Kein Original-Audio → Musik direkt als Track
|
||||
cmd = [
|
||||
"ffmpeg", "-y",
|
||||
"-i", str(video_path),
|
||||
"-stream_loop", "-1",
|
||||
"-i", str(music_file),
|
||||
"-filter_complex", f"[1:a]volume={volume_music}[a]",
|
||||
"-c:v", "copy",
|
||||
"-c:a", "aac",
|
||||
"-map", "0:v:0",
|
||||
"-map", "[a]",
|
||||
"-shortest",
|
||||
str(output_path),
|
||||
]
|
||||
|
||||
result = _run(cmd)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"ffmpeg Fehler beim Musik-Mixing: {result.stderr}")
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
def add_music_from_config(
|
||||
video_path: Path,
|
||||
output_path: Path,
|
||||
config: dict,
|
||||
) -> Path:
|
||||
"""Musik aus Konfiguration auswählen und mixen."""
|
||||
music_files = get_music_files(config)
|
||||
if not music_files:
|
||||
raise FileNotFoundError(
|
||||
f"Keine Musikdateien in: {config['resources']['folder']}/music/"
|
||||
)
|
||||
|
||||
mode = config["music"]["mode"]
|
||||
music_file = pick_music_file(music_files, mode)
|
||||
|
||||
return mix_music(
|
||||
video_path=video_path,
|
||||
output_path=output_path,
|
||||
music_file=music_file,
|
||||
volume_original=config["music"]["volume_original"],
|
||||
volume_music=config["music"]["volume_music"],
|
||||
)
|
||||
Reference in New Issue
Block a user