#!/usr/bin/env python3
"""
IDRBT PoC Video — TTS Commentary + Subtitles
Uses edge-tts (en-IN-PrabhatNeural) to generate Indian male voiceover
and burns subtitles into the terminal recording.

Usage:
    python3 generate_poc_mp4.py
"""

import asyncio
import edge_tts
import subprocess
import os
import json

VIDEO_IN = "/home/workspace/IDRBT/poc-video/Terminal.webm"
OUTPUT = "/home/workspace/IDRBT/poc-video/idrbt-poc-final.mp4"
AUDIO_OUT = "/home/workspace/IDRBT/poc-video/narration.mp3"
SUBS_OUT = "/home/workspace/IDRBT/poc-video/subtitles.srt"

VOICE = "en-IN-PrabhatNeural"
RATE = "+15%"

COMMENTARY = """Welcome to the IDRBT Domain Registrar Portal security vulnerability proof of concept submitted to CERT-In. I am demonstrating how over thirty three API endpoints are accessible without any authentication on the production domain registration portal.

We begin by confirming the portal is live at registrar dot idrbt dot ac dot in. Then we check the Spring Boot actuator endpoint which reveals the backend technology stack.

The most critical finding is the user database endpoint. Using a simple curl command, we retrieve over five thousand four hundred user records from the slash api slash dr slash user slash all endpoint. Each record contains the user's email, phone number, and crucially a bcrypt password hash. Forty nine percent of records also contain live OTP hashes, and ninety seven percent have two factor authentication disabled. This means credentials can be cracked offline giving attackers portal access.

We can also retrieve the full record for any individual user by email address, and even deleted user records are still accessible. Two hundred nineteen supposedly deleted records contain complete password hashes and OTP data.

The orphan users endpoint reveals over one thousand accounts with ninety nine point eight percent assigned the Super Admin role by default. This is a critical registration bug.

The portal also leaks financial data. Over fifteen hundred invoice records totaling four point seven crore rupees are accessible without authentication. Organisation IDs are sequential integers making enumeration trivial.

Internal configuration including GST rates, TDS percentages, and domain pricing is exposed. Five separate endpoints allow checking whether any email address exists in the system enabling user enumeration. Contractor details including vendor emails are also leaked.

DNSSEC configuration metadata and Digital Signature Certificate proxy endpoints are publicly accessible. These expose infrastructure details and allow session enumeration.

In total, thirty three plus endpoints are unauthenticated. Five thousand five hundred seventy six unique users are affected. Four point seven crore rupees in billing data is exposed. Systemically the production environment is running UAT configuration, there is no vulnerability disclosure program, and the vendor was appointed without public tender.

This concludes the proof of concept. All vulnerabilities were live at time of testing. Read only GET requests only, no data was modified."""


async def generate_tts():
    """Generate TTS audio + metadata using edge-tts."""
    print(f"Generating TTS with voice: {VOICE} ...")

    communicate = edge_tts.Communicate(COMMENTARY, VOICE, rate=RATE)
    # Save audio + JSON metadata (timing info for subtitle generation)
    metadata_path = "/home/workspace/IDRBT/poc-video/narration_meta.json"
    await communicate.save(AUDIO_OUT, metadata_fname=metadata_path)

    # Convert metadata JSON Lines to SRT subtitles
    meta = []
    with open(metadata_path) as f:
        for line in f:
            if line.strip():
                meta.append(json.loads(line))

    subs = []
    idx = 1
    for item in meta:
        offset = item.get("offset", 0) / 1e7  # 100ns units to seconds
        duration = item.get("duration", 0) / 1e7
        text = item.get("text", "").strip()

        if not text or duration < 0.3:
            continue

        end = offset + duration
        start_ts = f"{int(offset//3600):02d}:{int((offset%3600)//60):02d}:{int(offset%60):02d},{int((offset%1)*1000):03d}"
        end_ts = f"{int(end//3600):02d}:{int((end%3600)//60):02d}:{int(end%60):02d},{int((end%1)*1000):03d}"

        subs.append(f"{idx}\n{start_ts} --> {end_ts}\n{text}\n")
        idx += 1

    with open(SUBS_OUT, "w") as f:
        f.write("\n".join(subs))

    os.remove(metadata_path)

    # Get audio duration
    result = subprocess.run(
        ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", AUDIO_OUT],
        capture_output=True, text=True
    )
    info = json.loads(result.stdout)
    duration = float(info["format"]["duration"])
    print(f"  Audio duration: {duration:.2f}s")
    print(f"  Subtitles: {len(subs)} entries")
    return duration


def get_video_duration(path):
    """Get video duration in seconds."""
    result = subprocess.run(
        ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", path],
        capture_output=True, text=True
    )
    info = json.loads(result.stdout)
    return float(info["format"]["duration"])


def fix_srt_timing(srt_path, audio_dur, video_dur):
    """Scale subtitle timing to match video if audio is shorter, or stretch if longer."""
    if abs(audio_dur - video_dur) < 1:
        return  # Close enough

    scale = video_dur / audio_dur
    print(f"  Scaling subtitle timings by {scale:.3f} (audio={audio_dur:.1f}s, video={video_dur:.1f}s)")

    with open(srt_path) as f:
        lines = f.readlines()

    import re
    ts_pat = re.compile(r"(\d{2}):(\d{2}):(\d{2}),(\d{3})")
    new_lines = []
    for line in lines:
        def repl(m):
            h, m_, s, ms = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
            total = (h * 3600 + m_ * 60 + s + ms / 1000) * scale
            h2 = int(total // 3600)
            m2 = int((total % 3600) // 60)
            s2 = int(total % 60)
            ms2 = int((total - int(total)) * 1000)
            return f"{h2:02d}:{m2:02d}:{s2:02d},{ms2:03d}"
        new_lines.append(ts_pat.sub(repl, line))

    with open(srt_path, "w") as f:
        f.writelines(new_lines)

    print("  Subtitle timing adjusted.")


def render_final(audio_path, subs_path, video_dur, audio_dur):
    """
    Render final MP4 by:
    1. If audio longer than video, speed up audio with atempo to match
    2. Re-encode WebM to H.264 MP4
    3. Adding TTS audio (replacing original)
    4. Burning subtitles into the video
    """
    temp_video = "/home/workspace/IDRBT/poc-video/temp_narration.mp4"
    print(f"\nRendering final MP4...")

    adjusted_audio = audio_path
    srt_to_use = subs_path

    if audio_dur > video_dur * 1.05:
        # Speed up audio to match video duration
        atempo_val = audio_dur / video_dur
        # atempo cap is 2.0, chain if needed
        if atempo_val > 2.0:
            atempo_val = 2.0
            print(f"  ⚠ Audio much longer, capping atempo to 2.0")
        adjusted_audio = "/home/workspace/IDRBT/poc-video/temp_audio_sped.mp3"
        print(f"  Speeding up audio: {audio_dur:.1f}s → {video_dur:.1f}s (atempo={atempo_val:.3f})")
        subprocess.run([
            "ffmpeg", "-y",
            "-i", audio_path,
            "-af", f"atempo={atempo_val}",
            "-acodec", "libmp3lame",
            adjusted_audio
        ], check=True, capture_output=True)

        # Scale subtitles by the same factor
        fix_srt_timing(subs_path, audio_dur, video_dur)
        srt_to_use = subs_path

    elif audio_dur < video_dur * 0.95:
        # Pad audio if shorter
        adjusted_audio = "/home/workspace/IDRBT/poc-video/temp_audio_padded.mp3"
        pad_needed = video_dur - audio_dur
        print(f"  Padding audio: {audio_dur:.1f}s → {video_dur:.1f}s (+{pad_needed:.1f}s)")
        subprocess.run([
            "ffmpeg", "-y",
            "-i", audio_path,
            "-af", f"apad=pad_dur={pad_needed}",
            "-acodec", "libmp3lame",
            adjusted_audio
        ], check=True, capture_output=True)
    else:
        print(f"  Audio duration {audio_dur:.1f}s ≈ video {video_dur:.1f}s — no adjustment needed")

    # Combine: re-encode WebM to H.264 MP4 with burned subs + new audio
    cmd = [
        "ffmpeg", "-y",
        "-i", VIDEO_IN,
        "-i", adjusted_audio,
        "-c:v", "libx264",
        "-preset", "medium",
        "-crf", "23",
        "-vf", f"subtitles={srt_to_use}",
        "-c:a", "aac",
        "-b:a", "128k",
        "-map", "0:v:0",
        "-map", "1:a:0",
        "-shortest",
        "-movflags", "+faststart",
        OUTPUT
    ]

    subprocess.run(cmd, check=True)
    print(f"\n✓ Final video: {OUTPUT}")


async def main():
    print("=" * 60)
    print("IDRBT PoC Video — TTS Narration Generator")
    print("=" * 60)
    print()

    video_dur = get_video_duration(VIDEO_IN)
    print(f"Video: {VIDEO_IN}")
    print(f"  Duration: {video_dur:.2f}s")
    print(f"  Size: {os.path.getsize(VIDEO_IN) / 1024 / 1024:.1f} MB")
    print()

    print(f"Voice: {VOICE}")
    print(f"Commentary: {len(COMMENTARY.split())} words")
    print()

    # Generate TTS audio + subtitles
    audio_dur = await generate_tts()

    # Adjust subtitles timing if needed (handled inside render_final)
    # Render final video
    render_final(AUDIO_OUT, SUBS_OUT, video_dur, audio_dur)

    # Verify output
    result = subprocess.run(
        ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", OUTPUT],
        capture_output=True, text=True
    )
    info = json.loads(result.stdout)
    out_dur = float(info["format"]["duration"])
    out_size = int(info["format"]["size"])
    print(f"\nOutput verification:")
    print(f"  File: {OUTPUT}")
    print(f"  Duration: {out_dur:.2f}s")
    print(f"  Size: {out_size / 1024 / 1024:.1f} MB")
    for s in info["streams"]:
        if s["codec_type"] == "video":
            print(f"  Video: {s['codec_name']} {s['width']}x{s['height']}")
        elif s["codec_type"] == "audio":
            print(f"  Audio: {s['codec_name']} {s.get('sample_rate','?')} Hz")

    # Cleanup temp files
    for f in [AUDIO_OUT, SUBS_OUT, "/home/workspace/IDRBT/poc-video/temp_narration.mp3",
              "/home/workspace/IDRBT/poc-video/temp_narration_padded.mp3",
              "/home/workspace/IDRBT/poc-video/temp_video.mp4"]:
        if os.path.exists(f):
            os.remove(f)

    print("\n✓ Done!")


if __name__ == "__main__":
    asyncio.run(main())
