#!/usr/bin/env python3
"""
silence_cutter.py

Detects silence in a video's audio track and produces a new video with
the silent stretches removed (a "jump cut" edit) - same resolution/audio,
just tighter, with the dead air gone.

Usage:
    python3 silence_cutter.py INPUT.mp4 [OUTPUT.mp4]

Requires: ffmpeg (and ffprobe) available on PATH.
"""

import subprocess
import re
import sys
import os

SILENCE_THRESHOLD_DB = -30
MIN_SILENCE_DURATION = 0.5
PADDING = 0.15


def get_duration(path):
    result = subprocess.run(
        ["ffprobe", "-v", "error", "-show_entries", "format=duration",
         "-of", "default=noprint_wrappers=1:nokey=1", path],
        capture_output=True, text=True, check=True
    )
    return float(result.stdout.strip())


def detect_silence(path, threshold_db, min_duration):
    cmd = [
        "ffmpeg", "-i", path,
        "-af", f"silencedetect=noise={threshold_db}dB:d={min_duration}",
        "-f", "null", "-"
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    log = result.stderr

    starts = [float(m) for m in re.findall(r"silence_start:\s*([\d.]+)", log)]
    ends = [float(m) for m in re.findall(r"silence_end:\s*([\d.]+)", log)]

    silences = list(zip(starts, ends))
    if len(starts) > len(ends):
        silences.append((starts[-1], None))

    return silences


def build_keep_segments(silences, total_duration, padding):
    keep = []
    cursor = 0.0

    for s_start, s_end in silences:
        if s_end is None:
            s_end = total_duration

        seg_start = cursor
        seg_end = s_start + padding
        if seg_end > seg_start:
            keep.append((max(0, seg_start), min(seg_end, total_duration)))

        cursor = max(0, s_end - padding)

    if cursor < total_duration:
        keep.append((cursor, total_duration))

    merged = []
    for seg in keep:
        if merged and seg[0] <= merged[-1][1]:
            merged[-1] = (merged[-1][0], max(merged[-1][1], seg[1]))
        else:
            merged.append(seg)

    return merged


def cut_and_concat(input_path, output_path, segments):
    n = len(segments)

    filter_parts = []
    concat_inputs = []
    for i, (start, end) in enumerate(segments):
        filter_parts.append(
            f"[0:v]trim=start={start}:end={end},setpts=PTS-STARTPTS[v{i}];"
        )
        filter_parts.append(
            f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[a{i}];"
        )
        concat_inputs.append(f"[v{i}][a{i}]")

    filter_complex = "".join(filter_parts)
    filter_complex += f"{''.join(concat_inputs)}concat=n={n}:v=1:a=1[outv][outa]"

    cmd = [
        "ffmpeg", "-y", "-i", input_path,
        "-filter_complex", filter_complex,
        "-map", "[outv]", "-map", "[outa]",
        "-c:v", "libx264", "-preset", "veryfast",
        "-c:a", "aac",
        output_path
    ]
    subprocess.run(cmd, capture_output=True, text=True, check=True)


def main():
    if len(sys.argv) < 2:
        print("Usage: python3 silence_cutter.py INPUT.mp4 [OUTPUT.mp4]")
        sys.exit(1)

    input_path = sys.argv[1]
    if not os.path.isfile(input_path):
        print(f"Error: input file not found: {input_path}")
        sys.exit(1)

    if len(sys.argv) >= 3:
        output_path = sys.argv[2]
    else:
        input_dir = os.path.dirname(os.path.abspath(input_path))
        edited_dir = os.path.join(input_dir, "edited")
        os.makedirs(edited_dir, exist_ok=True)
        base = os.path.splitext(os.path.basename(input_path))[0]
        output_path = os.path.join(edited_dir, f"{base}_cut.mp4")

    print(f"Analyzing: {input_path}")
    total_duration = get_duration(input_path)
    print(f"Total duration: {total_duration:.2f}s")

    silences = detect_silence(input_path, SILENCE_THRESHOLD_DB, MIN_SILENCE_DURATION)
    print(f"Found {len(silences)} silent stretch(es)")

    segments = build_keep_segments(silences, total_duration, PADDING)
    kept_duration = sum(e - s for s, e in segments)
    print(f"Keeping {len(segments)} segment(s), total {kept_duration:.2f}s "
          f"(removed {total_duration - kept_duration:.2f}s of silence)")

    print(f"Rendering output: {output_path}")
    cut_and_concat(input_path, output_path, segments)
    print("Done.")


if __name__ == "__main__":
    main()
