Add docstrings for tooling and pipeline helpers

This commit is contained in:
hbrain 2026-05-28 14:52:16 +00:00
parent 2ac00e0e67
commit 8ae46752f6

View file

@ -55,6 +55,15 @@ LOG_PATH: Path | None = None
def tool_path(local_path: Path, fallback: str) -> str:
"""Return the preferred executable path for ffmpeg/ffprobe style tools.
Args:
local_path: Path to a bundled copy shipped with movmaker.
fallback: Name of the system executable to call when the bundled copy is missing.
Returns:
The fully-qualified path to execute.
"""
return str(local_path) if local_path.exists() else fallback
@ -76,12 +85,14 @@ class MediaItem:
def log_message(message: str) -> None:
"""Append a diagnostic line to the current render log if it is enabled."""
if LOG_PATH:
with LOG_PATH.open("a", encoding="utf-8") as log:
log.write(message.rstrip() + "\n")
def ffmpeg_progress_cmd(cmd: list[str], progress: bool) -> list[str]:
"""Inject ffmpeg-friendly progress flags when progress monitoring is requested."""
if not cmd or Path(cmd[0]).name != "ffmpeg":
return cmd
out = [cmd[0], "-hide_banner", "-nostats"]
@ -104,6 +115,12 @@ def format_bytes(value: int) -> str:
def run(cmd: list[str], progress_duration: float | None = None) -> None:
"""Execute a subprocess while streaming progress and teeing output to the log.
Args:
cmd: argv array ready for subprocess.run.
progress_duration: Expected output duration in seconds used to estimate progress; omit for instant commands.
"""
progress = progress_duration is not None and progress_duration > 0 and Path(cmd[0]).name == "ffmpeg"
run_cmd = ffmpeg_progress_cmd(cmd, progress)
log_message("+ " + shlex.join(run_cmd))
@ -483,6 +500,7 @@ def media_date(path: Path) -> datetime:
def ascii_safe(value: str) -> str:
"""Return a human-readable ASCII approximation of Scandinavian/Slavic letters."""
replacements = {
# Danish letters
"æ": "ae", "Æ": "Ae",
@ -505,6 +523,7 @@ def ascii_safe(value: str) -> str:
def slugify(value: str) -> str:
"""Convert an arbitrary string into a filesystem-safe slug."""
value = ascii_safe(value)
value = value.strip().lower()
value = re.sub(r"[^a-z0-9]+", "_", value)
@ -525,6 +544,7 @@ def display_date(dt: datetime) -> str:
def prepare_clip_stamps(media: list[MediaItem], data: MovieData) -> list[str]:
"""Build per-clip stamp strings containing title/date/place metadata."""
geocode_cache: dict[tuple[float, float], str | None] = {}
stamps: list[str] = []
for item in media:
@ -566,6 +586,7 @@ def natural_key(path: Path) -> list[object]:
def scan_input(input_dir: Path, image_duration: float) -> tuple[list[MediaItem], list[Path], MovieData]:
"""Detect media/audio/data files inside the input directory and build metadata."""
if not input_dir.is_dir():
raise SystemExit(f"Input directory does not exist: {input_dir}")
@ -678,6 +699,15 @@ def drawtext_escape(text: str) -> str:
def render_intro_logo_frame(logo: Path, out: Path, width: int, height: int, alpha: float) -> None:
"""Build a single black frame with the intro logo centered and scaled.
Args:
logo: Source PNG to overlay.
out: Destination PNG path for the composed frame.
width: Target video width.
height: Target video height.
alpha: Desired opacity between 0 and 1.
"""
if width <= 0 or height <= 0:
raise ValueError("intro logo frame must have positive width/height")
alpha = min(1.0, max(0.0, alpha))
@ -703,6 +733,7 @@ def render_intro_logo_frame(logo: Path, out: Path, width: int, height: int, alph
def normalize_clip(item: MediaItem, out: Path, width: int, height: int, fps: int, preset: str, crf: int) -> None:
"""Transcode one media item into a padded, FPS-normalized H.264 clip."""
vf = (
f"scale={width}:{height}:force_original_aspect_ratio=decrease,"
f"pad={width}:{height}:(ow-iw)/2:(oh-ih)/2:black,"
@ -773,6 +804,7 @@ def load_jamendo_client_id() -> str | None:
def download_jamendo_music(genre: str, out_dir: Path) -> Path | None:
"""Download a random Jamendo track matching the requested genre."""
client_id = load_jamendo_client_id()
if not client_id:
console("No audio files found and no Jamendo client ID configured; continuing without music.")
@ -843,6 +875,7 @@ def download_jamendo_music(genre: str, out_dir: Path) -> Path | None:
def concat_or_mix_audio(audio_files: list[Path], out: Path) -> Path | None:
"""Concatenate or mix multiple audio tracks into one temp file."""
if not audio_files:
return None
if len(audio_files) == 1:
@ -885,6 +918,7 @@ def build_video(
intro_visible_lead: float = 0.0,
skip_video_fade_out: bool = False,
) -> None:
"""Assemble normalized clips, overlays, and audio into the final MP4."""
output.parent.mkdir(parents=True, exist_ok=True)
text_file_count = 0
@ -1144,6 +1178,7 @@ def build_video(
def main(argv: list[str] | None = None) -> int:
"""CLI entry point for movmaker."""
parser = argparse.ArgumentParser(description="Make a video from a flat folder of images/videos/audio.")
parser.add_argument("input", type=Path, help="flat input directory")
parser.add_argument("--out-dir", type=Path, default=Path("."), help="directory for auto-named output videos")