#!/usr/bin/env python3
"""
video_edit.py — Local video editing via FFmpeg.

Operations supported:
  cut-silence    Remove silent portions
  cut-segment    Extract a time range
  speed          Change playback speed
  merge          Concatenate multiple videos
  add-music      Overlay audio track
  export-format  Convert to format/codec

Usage:
    python3 video_edit.py <operation> [options]

Requires: ffmpeg and ffprobe in /home/claw/.local/tools/ffmpeg/
"""

import argparse
import json
import subprocess
import sys
import os
from pathlib import Path

FFMPEG = "/home/claw/.local/tools/ffmpeg/ffmpeg"
FFPROBE = "/home/claw/.local/tools/ffmpeg/ffprobe"
WORKDIR = Path.home() / ".openclaw" / "workspace" / "tmp"
WORKDIR.mkdir(parents=True, exist_ok=True)


def probe(video_path):
    """Return duration, size, codec info via ffprobe."""
    cmd = [
        FFPROBE, "-v", "error",
        "-show_entries", "format=duration,size:stream=codec_name,codec_type,index",
        "-of", "json", str(video_path)
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    return json.loads(result.stdout)


def op_cut_silence(input_video, output_video, threshold_db=-30, min_duration=0.5, padding=0.1):
    """Remove silent segments using silencedetect."""
    # Use silencedetect to get silence boundaries
    cmd = [
        FFMPEG, "-i", str(input_video),
        "-af", f"silencedetect=noise={threshold_db}dB:d={min_duration}",
        "-f", "null", "-"
    ]
    r = subprocess.run(cmd, capture_output=True, text=True)
    lines = r.stderr.splitlines()

    import re
    start_times, end_times = [], []
    for line in lines:
        m = re.search(r"silence_start:\s*([\d.]+)", line)
        if m:
            start_times.append(float(m.group(1)))
        m = re.search(r"silence_end:\s*([\d.]+)\s*\|\s*silence_duration:\s*([\d.]+)", line)
        if m:
            end_times.append(float(m.group(1)))

    total_dur = float(probe(input_video)["format"]["duration"])

    # Build keep segments
    segments, last_end = [], 0.0
    for s, e in zip(start_times, end_times):
        s = max(last_end, s - padding)
        e = min(total_dur, e + padding)
        if s > last_end:
            segments.append((last_end, s))
        last_end = e
    if last_end < total_dur:
        segments.append((last_end, total_dur))

    if not segments:
        print("[WARN] No segments to keep — video may have no silences")
        return False

    # Write concat list
    list_file = WORKDIR / "concat_segments.txt"
    tmp_files = []
    with open(list_file, "w") as f:
        for i, (s, e) in enumerate(segments):
            seg_file = WORKDIR / f"seg_{i:03d}.mp4"
            tmp_files.append(seg_file)
            subprocess.run([
                FFMPEG, "-y", "-ss", str(s), "-i", str(input_video),
                "-t", str(e - s),
                "-c:v", "libx264", "-preset", "fast", "-crf", "23",
                "-c:a", "aac", "-b:a", "128k",
                str(seg_file)
            ], capture_output=True)
            f.write(f"file '{seg_file}'\n")

    ok = subprocess.run([
        FFMPEG, "-y", "-f", "concat", "-safe", "0",
        "-i", str(list_file),
        "-c:v", "libx264", "-preset", "fast", "-crf", "23",
        "-c:a", "aac", "-b:a", "128k",
        str(output_video)
    ], capture_output=True).returncode == 0

    # Cleanup
    list_file.unlink()
    for seg in tmp_files:
        if seg.exists():
            seg.unlink()

    return ok


def op_cut_segment(input_video, output_video, start=None, end=None):
    """Extract a time range from the video."""
    cmd = [FFMPEG, "-y"]
    if start is not None:
        cmd += ["-ss", str(start)]
    cmd += ["-i", str(input_video)]
    if end is not None:
        cmd += ["-t", str(end - start if start else end)]
    cmd += [
        "-c:v", "libx264", "-preset", "fast", "-crf", "23",
        "-c:a", "aac", "-b:a", "128k",
        str(output_video)
    ]
    return subprocess.run(cmd, capture_output=True).returncode == 0


def op_speed(input_video, output_video, factor=2.0):
    """Speed up or slow down video by factor."""
    if factor <= 0:
        print("[ERROR] Speed factor must be > 0")
        return False
    cmd = [
        FFMPEG, "-y", "-i", str(input_video),
        "-filter:v", f"setpts={1/factor}*PTS",
        "-filter:a", f"atempo={min(max(factor, 0.5), 2.0)}",
        "-c:v", "libx264", "-preset", "fast", "-crf", "23",
        "-c:a", "aac", "-b:a", "128k",
        str(output_video)
    ]
    return subprocess.run(cmd, capture_output=True).returncode == 0


def op_merge(input_videos, output_video):
    """Concatenate multiple videos into one."""
    list_file = WORKDIR / "merge_list.txt"
    with open(list_file, "w") as f:
        for v in input_videos:
            f.write(f"file '{Path(v).resolve()}'\n")
    ok = subprocess.run([
        FFMPEG, "-y", "-f", "concat", "-safe", "0",
        "-i", str(list_file),
        "-c:v", "libx264", "-preset", "fast", "-crf", "23",
        "-c:a", "aac", "-b:a", "128k",
        str(output_video)
    ], capture_output=True).returncode == 0
    list_file.unlink()
    return ok


def op_add_music(video_path, audio_path, output_path, music_volume=0.3):
    """Overlay background music on video."""
    cmd = [
        FFMPEG, "-y",
        "-i", str(video_path), "-i", str(audio_path),
        "-filter_complex",
        f"[1:a]volume={music_volume}[music];[0:a][music]amix=inputs=2:duration=first",
        "-c:v", "libx264", "-preset", "fast", "-crf", "23",
        "-c:a", "aac", "-b:a", "128k",
        str(output_path)
    ]
    return subprocess.run(cmd, capture_output=True).returncode == 0


def op_export_format(input_video, output_video, fmt="mp4", crf=23):
    """Export video to a different format/codec."""
    if fmt == "mp4":
        codec = "-c:v libx264 -preset fast -crf {} -c:a aac -b:a 128k"
    elif fmt == "webm":
        codec = "-c:v libvpx-vp9 -crf {} -b:v 0 -c:a libopus"
    elif fmt == "mov":
        codec = "-c:v libx264 -preset fast -crf {} -c:a aac -b:a 128k"
    else:
        codec = "-c copy -c:a copy"
    cmd = [
        FFMPEG, "-y", "-i", str(input_video),
        "-c:v", "libx264", "-preset", "fast", "-crf", str(crf),
        "-c:a", "aac", "-b:a", "128k",
        str(output_video)
    ]
    return subprocess.run(cmd, capture_output=True).returncode == 0


def main():
    parser = argparse.ArgumentParser(description="Local video editing via FFmpeg")
    parser.add_argument("operation", choices=[
        "cut-silence", "cut-segment", "speed", "merge", "add-music", "export-format"
    ], help="Editing operation")
    parser.add_argument("args", nargs=argparse.REMAINDER, help="Operation arguments")

    args = parser.parse_args()
    ops = {
        "cut-silence":    ("<input> <output> [--threshold-db DB] [--min-duration SEC]", 2),
        "cut-segment":   ("<input> <output> --start SEC --end SEC", 4),
        "speed":         ("<input> <output> --factor FLOAT", 3),
        "merge":         ("<input1> <input2> [...] <output>", 3),
        "add-music":     ("<video> <audio> <output> [--volume FLOAT]", 3),
        "export-format": ("<input> <output> --format mp4|webm|mov", 2),
    }
    desc, min_args = ops[args.operation]
    # simple dispatch by operation name
    if args.operation == "cut-silence":
        p = argparse.ArgumentParser()
        p.add_argument("input_video"); p.add_argument("output_video")
        p.add_argument("--threshold-db", type=float, default=-30)
        p.add_argument("--min-duration", type=float, default=0.5)
        p.add_argument("--padding", type=float, default=0.1)
        a = p.parse_args(args.args)
        ok = op_cut_silence(a.input_video, a.output_video, a.threshold_db, a.min_duration, a.padding)
    elif args.operation == "cut-segment":
        p = argparse.ArgumentParser()
        p.add_argument("input_video"); p.add_argument("output_video")
        p.add_argument("--start", type=float, required=True)
        p.add_argument("--end", type=float, required=True)
        a = p.parse_args(args.args)
        ok = op_cut_segment(a.input_video, a.output_video, a.start, a.end)
    elif args.operation == "speed":
        p = argparse.ArgumentParser()
        p.add_argument("input_video"); p.add_argument("output_video")
        p.add_argument("--factor", type=float, default=2.0)
        a = p.parse_args(args.args)
        ok = op_speed(a.input_video, a.output_video, a.factor)
    elif args.operation == "merge":
        p = argparse.ArgumentParser()
        p.add_argument("input_videos", nargs="+")
        p.add_argument("output_video")
        a = p.parse_args(args.args)
        ok = op_merge(a.input_videos[:-1], a.input_videos[-1])
    elif args.operation == "add-music":
        p = argparse.ArgumentParser()
        p.add_argument("video_path"); p.add_argument("audio_path")
        p.add_argument("output_path"); p.add_argument("--volume", type=float, default=0.3)
        a = p.parse_args(args.args)
        ok = op_add_music(a.video_path, a.audio_path, a.output_path, a.volume)
    elif args.operation == "export-format":
        p = argparse.ArgumentParser()
        p.add_argument("input_video"); p.add_argument("output_video")
        p.add_argument("--format", default="mp4")
        p.add_argument("--crf", type=int, default=23)
        a = p.parse_args(args.args)
        ok = op_export_format(a.input_video, a.output_video, a.format, a.crf)

    if ok:
        print(f"[OK] {args.operation} → completed")
    else:
        print(f"[FAIL] {args.operation} → failed")
        sys.exit(1)


if __name__ == "__main__":
    main()