#!/usr/bin/env python3
"""Podcast / audiogram clip: a speech segment -> a vertical (or square / wide) video with cover art, title, speaker,
a live waveform, a progress bar and burned captions.

Examples
  python podcast.py --audio https://.../episode.mp3 --start 754 --end 812 --cover https://.../cover.png \
      --title "Why most diets fail" --speaker "Dr. Mia Hart - The Long Run Podcast" --captions "transcript of the segment..."
  python podcast.py --audio speech.mp3 --srt segment.srt --aspect 1:1 --accent "#22D3EE"

--audio can be a video file too (its sound is used). --srt timings are relative to --start.
"""
from __future__ import annotations

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import kmedia as K  # noqa: E402


def layout(W: int, H: int, cover, title: str, speaker: str, accent, font, track):
    """Opaque card with a transparent hole where the progress bar shows through."""
    from PIL import Image, ImageDraw, ImageFilter
    short = min(W, H)
    if cover is not None:
        bg = K.cover(cover.convert("RGB"), W, H).filter(ImageFilter.GaussianBlur(short / 25))
        bg = Image.blend(bg, Image.new("RGB", (W, H), (0, 0, 0)), 0.45)
    else:
        bg = K.gradient(W, H, tuple(int(v * 0.35) for v in accent), (10, 10, 14))
    img = bg.convert("RGBA")
    vertical = H > W
    cs = int(short * (0.62 if vertical else 0.5))
    cx = (W - cs) // 2 if vertical or W == H else int(W * 0.06)
    cy = int(H * 0.08) if vertical else (H - cs) // 2 if W > H else int(H * 0.06)
    if W == H:
        cs = int(short * 0.42)
        cx, cy = (W - cs) // 2, int(H * 0.06)
    art = K.cover(cover.convert("RGB"), cs, cs) if cover is not None else K.gradient(cs, cs, accent, (20, 20, 30))
    mask = Image.new("L", (cs, cs), 0)
    ImageDraw.Draw(mask).rounded_rectangle((0, 0, cs - 1, cs - 1), radius=cs // 14, fill=255)
    shadow = Image.new("RGBA", (cs + 60, cs + 60), (0, 0, 0, 0))
    ImageDraw.Draw(shadow).rounded_rectangle((30, 40, cs + 30, cs + 40), radius=cs // 14, fill=(0, 0, 0, 150))
    img.alpha_composite(shadow.filter(ImageFilter.GaussianBlur(20)), (cx - 30, cy - 30))
    img.paste(art, (cx, cy), mask)
    if vertical or W == H:
        tx0, tx1 = int(W * 0.08), int(W * 0.92)
        ty = cy + cs + int(short * 0.05)
    else:
        tx0, tx1 = cx + cs + int(W * 0.05), int(W * 0.94)
        ty = cy
    r = K.draw_block(img, title, (tx0, ty, tx1, ty + int(short * 0.16)), fill=(255, 255, 255), align="center" if (vertical or W == H) else "left",
                     valign="top", max_size=int(short * 0.075), min_size=18, kind="bold", font_override=font, max_lines=2)
    y2 = r["bbox"][3] + int(short * 0.02)
    if speaker:
        r2 = K.draw_block(img, speaker, (tx0, y2, tx1, y2 + int(short * 0.06)), fill=(210, 210, 215), valign="top",
                          align="center" if (vertical or W == H) else "left", max_size=int(short * 0.038), min_size=14, kind="regular", max_lines=1)
        y2 = r2["bbox"][3]
    # hole for the progress bar
    x0, y0, x1, y1 = track
    hole = Image.new("RGBA", (x1 - x0, y1 - y0), (0, 0, 0, 0))
    img.paste(hole, (x0, y0))
    return img, {"text_bottom": y2, "art": (cx, cy, cs), "tx": (tx0, tx1)}


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--audio", required=True, help="audio or video URL / file")
    ap.add_argument("--start", type=float, default=0.0)
    ap.add_argument("--end", type=float, default=0.0, help="0 = start + 60 s or the end")
    ap.add_argument("--cover", default="", help="cover art / host photo")
    ap.add_argument("--title", default="")
    ap.add_argument("--speaker", default="")
    ap.add_argument("--srt", default="")
    ap.add_argument("--captions", default="", help="transcript text for auto-timed captions")
    ap.add_argument("--caption-style", choices=K.CAPTION_STYLES, default="karaoke")
    ap.add_argument("--accent", default="#FFD60A")
    ap.add_argument("--aspect", default="9:16")
    ap.add_argument("--res", type=int, default=1080)
    ap.add_argument("--fps", type=int, default=30)
    K.add_font_args(ap)
    ap.add_argument("--out", default="kenerate-out/podcast-clip/clip.mp4")
    ap.add_argument("--keep-temp", action="store_true")
    a = K.parse_args(ap)

    st = K.Status(5, "podcast-clip")
    K.require(status=st)
    W, H = K.size_for(a.aspect, a.res)
    tmp = K.workdir(a.keep_temp)
    out = Path(a.out)
    out.parent.mkdir(parents=True, exist_ok=True)
    accent = K.hex_rgb(a.accent)

    st.step("Fetching audio" + (" + cover" if a.cover else ""))
    src = K.fetch(a.audio, tmp, "audio", st)
    info = K.probe(src)
    if not info["has_audio"]:
        K.fail("The input has no audio track.")
    end = a.end or min(info["duration"], a.start + 60)
    D = max(1.0, min(end, info["duration"]) - a.start)
    cover = K.open_image(K.fetch(a.cover, tmp, "cover", st)) if a.cover else None

    st.step(f"Cutting {D:.1f} s and laying out {W}x{H}")
    seg = tmp / "seg.m4a"
    K.run_ffmpeg(["-ss", f"{a.start:.3f}", "-t", f"{D:.3f}", "-i", str(src), "-vn", "-af", "loudnorm=I=-16:TP=-1.5:LRA=11"]
                 + K.AUDIO_ARGS + [str(seg)], duration=D, status=st)
    short = min(W, H)
    vertical = H > W
    wave_h = int(short * 0.16)
    lay, meta = layout(W, H, cover, a.title, a.speaker, accent, a.font, (0, 0, 1, 1))
    wx0, wx1 = meta["tx"]
    wy = meta["text_bottom"] + int(short * 0.04)
    track_y = min(H - int(short * 0.08), wy + wave_h + int(short * 0.03))
    track = (wx0, track_y, wx1, track_y + max(6, short // 140))
    lay, meta = layout(W, H, cover, a.title, a.speaker, accent, a.font, track)
    lp = tmp / "layout.png"
    lay.save(lp)
    ww = (wx1 - wx0) // 2 * 2
    wave_h = wave_h // 2 * 2
    tw = track[2] - track[0]
    th = track[3] - track[1]
    hexacc = K.rgb_hex(accent).lstrip("#")

    cues = K.cues_from_args(a.srt or None, a.captions or None, D)
    st.step(f"Rendering captions ({len(cues)})" if cues else "Captions (none)")
    cap_args, cap_graph = [], ""
    if cues:
        lst = K.caption_track(cues, W, H, D, tmp / "caps", a.caption_style,
                              "bottom" if vertical else "bottom", a.font, accent=accent, status=st)
        cap_args = ["-f", "concat", "-safe", "0", "-i", str(lst)]

    st.step("Encoding")
    graph = (f"color=c=0x2A2A30:s={W}x{H}:r={a.fps}:d={D:.3f}[base];"
             f"color=c=0x{hexacc}:s={tw}x{th}:r={a.fps}:d={D:.3f}[pb];"
             f"[base][pb]overlay=x='{track[0]}-{tw}+{tw}*t/{D:.3f}':y={track[1]}[b1];"
             f"[b1][0:v]overlay=0:0[b2];"
             f"[1:a]showwaves=s={ww}x{wave_h}:mode=cline:rate={a.fps}:colors=0x{hexacc}|0x{hexacc}:scale=sqrt:draw=full,format=rgba[wv];"
             f"[b2][wv]overlay={wx0}:{wy}[b3]")
    last = "[b3]"
    if cues:
        graph += f";[2:v]format=rgba[cp];{last}[cp]overlay=0:0:eof_action=pass[b4]"
        last = "[b4]"
    graph += f";{last}format=yuv420p[v]"
    K.run_ffmpeg(["-loop", "1", "-framerate", str(a.fps), "-t", f"{D:.3f}", "-i", str(lp), "-i", str(seg)] + cap_args
                 + ["-filter_complex", graph, "-map", "[v]", "-map", "1:a", "-t", f"{D:.3f}"]
                 + K.video_codec_args(20) + K.AUDIO_ARGS + [str(out)], duration=D, status=st)
    if cues:
        K.write_srt(cues, out.with_suffix(".srt"))
    st.step("Done")
    K.finish({"output": K.file_info(out), "seconds": round(D, 2), "frame": f"{W}x{H}", "captions": len(cues),
              "elapsed_s": st.elapsed()})


if __name__ == "__main__":
    K.main_guard(main)
