"""
🎬 Movie Dubber — Any Language → Hindi
Single-file Streamlit app for Hugging Face Spaces (CPU, free tier)

ALL code is in this one file — no utils/ folder needed.
This is the most reliable way to avoid import errors on HF Spaces.

Pipeline:
  Upload MP4 → Extract Audio → Transcribe → Translate → TTS → Merge → Download
"""

import os
import gc
import math
import tempfile
import subprocess

import streamlit as st

# ─── Page config (MUST be first Streamlit call) ───────────────────────────────
st.set_page_config(
    page_title="🎬 Movie Dubber — Any Language → Hindi",
    page_icon="🎬",
    layout="wide",
    initial_sidebar_state="expanded",
)

st.markdown("""
<style>
.stProgress > div > div { background-color: #FF4B4B; }
.seg-card {
    background: #1a1a2e;
    border-left: 4px solid #FF4B4B;
    border-radius: 8px;
    padding: 10px 14px;
    margin: 6px 0;
}
.spk { background:#FF4B4B; color:#fff; border-radius:10px;
        padding:2px 9px; font-size:0.78em; font-weight:700; }
.ts  { color:#999; font-size:0.82em; }
</style>
""", unsafe_allow_html=True)


# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 1 — AUDIO UTILITIES
# ═══════════════════════════════════════════════════════════════════════════════

def extract_audio(video_path: str) -> str:
    """
    Pull audio from video using ffmpeg.
    Returns path to a 16 kHz mono WAV — ideal for Whisper.
    """
    out = os.path.join(tempfile.gettempdir(), "extracted_audio.wav")
    subprocess.run([
        "ffmpeg", "-y", "-i", video_path,
        "-vn",                   # no video
        "-acodec", "pcm_s16le",  # uncompressed WAV
        "-ar", "16000",          # 16 kHz
        "-ac", "1",              # mono
        out,
    ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    return out


def detect_silence_segments(audio_path: str):
    """Return list of (start_sec, end_sec) for silent / music regions."""
    from pydub import AudioSegment
    from pydub.silence import detect_nonsilent

    audio  = AudioSegment.from_wav(audio_path)
    speech = detect_nonsilent(audio, min_silence_len=700, silence_thresh=-40)

    total_ms = len(audio)
    silence, prev = [], 0
    for s, e in speech:
        if s > prev + 200:
            silence.append((prev / 1000, s / 1000))
        prev = e
    if prev < total_ms - 200:
        silence.append((prev / 1000, total_ms / 1000))
    return silence


# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 2 — TRANSCRIPTION
# ═══════════════════════════════════════════════════════════════════════════════

def transcribe_audio(audio_path: str, model_size: str = "base", progress_cb=None):
    """
    Transcribe audio with faster-whisper (INT8 quantised, CPU-optimised).
    Returns list of { start, end, text, speaker, lang }.
    """
    from faster_whisper import WhisperModel

    if progress_cb:
        progress_cb(5, f"Loading Whisper '{model_size}'…")

    model = WhisperModel(model_size, device="cpu", compute_type="int8")

    if progress_cb:
        progress_cb(20, "Transcribing…")

    raw_segs, info = model.transcribe(
        audio_path,
        beam_size=3,
        vad_filter=True,
        vad_parameters={"min_silence_duration_ms": 500},
        word_timestamps=True,
    )

    if progress_cb:
        progress_cb(50, f"Language detected: {info.language}")

    segments = []
    for i, seg in enumerate(raw_segs):
        text = seg.text.strip()
        if not text:
            continue
        speaker = _heuristic_speaker(segments, seg.start)
        segments.append({
            "start":   round(seg.start, 2),
            "end":     round(seg.end, 2),
            "text":    text,
            "speaker": speaker,
            "lang":    info.language,
        })
        if progress_cb and i % 5 == 0:
            progress_cb(min(90, 50 + i * 2), f"Segment {i+1} done…")

    del model
    gc.collect()
    return segments


def _heuristic_speaker(existing, current_start):
    """Flip speaker ID when gap > 1.5 s (lightweight alternative to pyannote)."""
    if not existing:
        return "1"
    if current_start - existing[-1]["end"] > 1.5:
        return "2" if existing[-1]["speaker"] == "1" else "1"
    return existing[-1]["speaker"]


# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 3 — TRANSLATION (Helsinki-NLP MarianMT)
# ═══════════════════════════════════════════════════════════════════════════════

_DIRECT_HI = {"en-hi": "Helsinki-NLP/opus-mt-en-hi"}
_TO_EN = {
    "de": "Helsinki-NLP/opus-mt-de-en",
    "fr": "Helsinki-NLP/opus-mt-fr-en",
    "es": "Helsinki-NLP/opus-mt-es-en",
    "it": "Helsinki-NLP/opus-mt-it-en",
    "pt": "Helsinki-NLP/opus-mt-pt-en",
    "ja": "Helsinki-NLP/opus-mt-ja-en",
    "zh": "Helsinki-NLP/opus-mt-zh-en",
    "ko": "Helsinki-NLP/opus-mt-ko-en",
    "ar": "Helsinki-NLP/opus-mt-ar-en",
    "ru": "Helsinki-NLP/opus-mt-ru-en",
    "tr": "Helsinki-NLP/opus-mt-tr-en",
    "nl": "Helsinki-NLP/opus-mt-nl-en",
    "pl": "Helsinki-NLP/opus-mt-pl-en",
}
_mt_cache = {}


def _load_mt(name):
    from transformers import MarianMTModel, MarianTokenizer
    if name not in _mt_cache:
        tok = MarianTokenizer.from_pretrained(name)
        mdl = MarianMTModel.from_pretrained(name)
        mdl.eval()
        _mt_cache[name] = (tok, mdl)
    return _mt_cache[name]


def _mt(text, model_name):
    tok, mdl = _load_mt(model_name)
    inp = tok([text], return_tensors="pt", padding=True,
              truncation=True, max_length=512)
    out = mdl.generate(**inp, num_beams=2, max_length=512)
    return tok.decode(out[0], skip_special_tokens=True).strip()


def translate_to_hindi(text: str, source_lang: str = "en") -> str:
    """Translate text → Hindi. Pivots through English if no direct model."""
    if not text.strip() or source_lang == "hi":
        return text
    src = source_lang.lower()
    # Direct path
    if f"{src}-hi" in _DIRECT_HI:
        try:
            return _mt(text, _DIRECT_HI[f"{src}-hi"])
        except Exception:
            pass
    # Pivot: src → English
    if src != "en" and src in _TO_EN:
        try:
            text = _mt(text, _TO_EN[src])
        except Exception:
            pass
    # English → Hindi
    try:
        return _mt(text, _DIRECT_HI["en-hi"])
    except Exception:
        return f"[Translation failed: {text}]"


# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 4 — HINDI TTS (gTTS — free, instant, no GPU)
# ═══════════════════════════════════════════════════════════════════════════════

# ── Voice options ─────────────────────────────────────────────────────────────
# gTTS uses Google's TTS via HTTP. Different TLDs give subtly different voices.
# We label them Male/Female as a UX convenience; the underlying difference is
# accent/server — gTTS doesn't expose true gender control, but the variation
# is clearly audible and useful for multi-speaker dubbing.
VOICE_OPTIONS = {
    "Male 1 (Standard)":    "com",       # US/international server
    "Male 2 (Indian)":      "co.in",     # Indian English server
    "Female 1 (UK)":        "co.uk",     # UK server — higher pitch
    "Female 2 (Australia)": "com.au",    # Australian server
    "Default":              "com",       # fallback
}

# Legacy alias kept so any existing code using SPEAKER_OPTIONS still works
SPEAKER_OPTIONS = VOICE_OPTIONS


def _text_to_wav(text: str, tld: str, out_path: str):
    """Hindi text → WAV via gTTS (MP3 intermediate)."""
    from gtts import gTTS
    from pydub import AudioSegment

    if not text.strip():
        AudioSegment.silent(500).export(out_path, format="wav")
        return

    mp3 = out_path.replace(".wav", ".mp3")
    gTTS(text=text, lang="hi", tld=tld, slow=False).save(mp3)
    AudioSegment.from_mp3(mp3).export(out_path, format="wav")
    try:
        os.remove(mp3)
    except Exception:
        pass


def _fit_to_duration(speech, target_ms):
    """Speed-fit TTS audio to match original segment duration."""
    from pydub import AudioSegment
    dur = len(speech)
    if not dur or not target_ms:
        return speech
    ratio = dur / target_ms
    if ratio > 1.3:
        mult = min(ratio, 1.8)
        speech = speech._spawn(
            speech.raw_data,
            overrides={"frame_rate": int(speech.frame_rate * mult)},
        ).set_frame_rate(speech.frame_rate)
    elif dur < target_ms:
        speech = speech + AudioSegment.silent(target_ms - dur)
    return speech


def generate_hindi_speech(
    segments,
    speaker="com",
    speaker_voice_map=None,
    speech_volume=1.2,
    progress_cb=None,
):
    """
    Generate full dubbed audio track.

    Supports per-speaker voices via speaker_voice_map:
        { "1": "com", "2": "co.uk", ... }

    If speaker_voice_map is None, all segments use `speaker` (tld string).
    Each segment → TTS with its speaker's voice → speed-fit → overlaid at
    correct timestamp on a silent base track.
    """
    from pydub import AudioSegment

    total_ms = int(max(s["end"] for s in segments) * 1000) + 2000
    track    = AudioSegment.silent(duration=total_ms)
    tmp, n   = tempfile.gettempdir(), len(segments)

    for i, seg in enumerate(segments):
        if progress_cb:
            progress_cb(int((i + 1) / n * 90), f"Synthesising {i+1}/{n}…")

        hindi = seg.get("hindi_text", "").strip()
        if not hindi:
            continue

        # Pick voice: per-speaker map → fallback to global speaker tld
        spk_id = str(seg.get("speaker", "1"))
        if speaker_voice_map and spk_id in speaker_voice_map:
            tld = speaker_voice_map[spk_id]
        else:
            tld = speaker

        start_ms  = int(seg["start"] * 1000)
        target_ms = int(seg["end"] * 1000) - start_ms
        wav = os.path.join(tmp, f"seg_{i:04d}.wav")

        try:
            _text_to_wav(hindi, tld, wav)
        except Exception:
            continue

        speech = AudioSegment.from_wav(wav)
        if speech_volume != 1.0:
            speech = speech + (20 * math.log10(max(speech_volume, 0.01)))
        speech = _fit_to_duration(speech, target_ms)
        track  = track.overlay(speech, position=start_ms)

        try:
            os.remove(wav)
        except Exception:
            pass
        gc.collect()

    out = os.path.join(tmp, "hindi_dubbed_audio.wav")
    track.export(out, format="wav")
    return out


# ── Transcription download helper ─────────────────────────────────────────────

def build_transcription_txt(segments: list) -> str:
    """
    Format transcribed segments into a readable .txt file.

    Output format per segment:
        [MM:SS - MM:SS] Speaker N:
        Original text

    Easy to read, copy, and share.
    """
    lines = []
    for seg in segments:
        start_m, start_s = divmod(int(seg["start"]), 60)
        end_m,   end_s   = divmod(int(seg["end"]),   60)
        ts = f"[{start_m:02d}:{start_s:02d} - {end_m:02d}:{end_s:02d}]"
        lines.append(f"{ts} Speaker {seg['speaker']}:")
        lines.append(seg["text"])
        lines.append("")   # blank line between segments
    return "\n".join(lines)


# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 5 — VIDEO MERGE (ffmpeg, no video re-encoding)
# ═══════════════════════════════════════════════════════════════════════════════

def merge_audio_video(video_path, dubbed_audio_path, bgm_volume=0.15):
    """
    Mix Hindi speech with faint original BGM, then mux into video.
    Video stream is COPIED (no re-encode) → fast even on CPU.
    """
    out = os.path.join(tempfile.gettempdir(), "hindi_dubbed_output.mp4")
    bgm = str(round(bgm_volume, 3))

    result = subprocess.run([
        "ffmpeg", "-y",
        "-i", video_path,
        "-i", dubbed_audio_path,
        "-filter_complex",
        f"[0:a]volume={bgm}[b];[b][1:a]amix=inputs=2:duration=first[mix]",
        "-map", "0:v", "-map", "[mix]",
        "-c:v", "copy", "-c:a", "aac", "-b:a", "192k", "-shortest",
        out,
    ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    if result.returncode != 0:
        # Fallback: replace audio entirely (skip BGM blend)
        fallback = os.path.join(tempfile.gettempdir(), "dubbed_fallback.mp4")
        subprocess.run([
            "ffmpeg", "-y",
            "-i", video_path, "-i", dubbed_audio_path,
            "-map", "0:v", "-map", "1:a",
            "-c:v", "copy", "-c:a", "aac", "-b:a", "192k", "-shortest",
            fallback,
        ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return fallback

    return out


# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 6 — SMART AUTO-ASSIGN
# Splits a Hindi dubbing text block and maps lines → segments
# Rule-based only — no ML models, fully CPU-safe
# ═══════════════════════════════════════════════════════════════════════════════

import re

# Emotion keyword lists for simple rule-based detection
_EMOTION_RULES = {
    "😠 Angry":  ["गुस्सा", "क्रोध", "चिल्लाओ", "मत करो", "बंद करो", "नहीं", "कभी नहीं"],
    "💕 Soft":   ["प्यार", "शांत", "धीरे", "माफ", "दुख", "रो", "आँसू"],
    "😄 Happy":  ["खुशी", "हाहाहा", "बढ़िया", "शाबाश", "वाह", "मज़ा", "जीत"],
    "😐 Neutral": [],  # default
}

def _detect_emotion(text: str) -> str:
    """Return emotion label for a Hindi line — pure keyword matching, no model."""
    for label, keywords in _EMOTION_RULES.items():
        if any(kw in text for kw in keywords):
            return label
    return "😐 Neutral"


def _split_hindi_text(raw: str) -> list:
    """
    Split Hindi paragraph into sentences.
    Splits on: newline, Hindi danda (।), ? !
    Strips whitespace and drops empty lines.
    """
    normalized = re.sub(r"[।?!\n]+", "\n", raw)
    return [ln.strip() for ln in normalized.splitlines() if ln.strip()]


def _merge_short_lines(lines: list, target: int) -> list:
    """If we have MORE lines than segments, merge the shortest adjacent pairs."""
    lines = list(lines)
    while len(lines) > target:
        # Find index of shortest line that has a neighbour
        idx = min(range(len(lines) - 1), key=lambda i: len(lines[i]))
        merged = lines[idx] + " " + lines[idx + 1]
        lines = lines[:idx] + [merged] + lines[idx + 2:]
    return lines


def _split_long_lines(lines: list, target: int) -> list:
    """If we have FEWER lines than segments, split the longest line at midpoint."""
    lines = list(lines)
    while len(lines) < target:
        idx = max(range(len(lines)), key=lambda i: len(lines[i]))
        line = lines[idx]
        mid = len(line) // 2

        # Find a natural break near the middle (comma or space)
        split_at = -1
        for offset in range(mid):
            for pos in [mid - offset, mid + offset]:
                if 0 < pos < len(line) and line[pos] in "،, ":
                    split_at = pos
                    break
            if split_at != -1:
                break
        if split_at == -1:
            split_at = mid  # fallback: hard split at middle

        p1 = line[:split_at].strip()
        p2 = line[split_at:].strip().lstrip("،, ")
        if not p1 or not p2:
            break  # can't split further

        lines = lines[:idx] + [p1, p2] + lines[idx + 1:]
    return lines


def auto_assign_hindi_text(segments: list, hindi_text: str) -> list:
    """
    Split a block of Hindi dubbing text and assign each line
    to the matching segment, preserving speaker + timestamps.

    Steps:
      1. Split text into lines on newline / danda / ? / !
      2. Adjust line count to match segment count:
         - Too many lines → merge shortest adjacent pairs
         - Too few lines  → split longest lines at midpoint
      3. Assign one line per segment
      4. Detect emotion per line (keyword-based, no model)
    """
    lines = _split_hindi_text(hindi_text)
    n = len(segments)
    if not lines:
        return segments

    if len(lines) > n:
        lines = _merge_short_lines(lines, n)
    elif len(lines) < n:
        lines = _split_long_lines(lines, n)

    # Safety pad
    while len(lines) < n:
        lines.append("")

    return [
        {**seg, "hindi_text": lines[i] if i < len(lines) else "",
         "emotion": _detect_emotion(lines[i] if i < len(lines) else "")}
        for i, seg in enumerate(segments)
    ]


# ═══════════════════════════════════════════════════════════════════════════════
# STREAMLIT UI
# ═══════════════════════════════════════════════════════════════════════════════

# Session state
for k in ["audio_path", "segments", "translated_segments",
          "dubbed_audio_path", "output_video_path", "speaker_voice_map"]:
    if k not in st.session_state:
        st.session_state[k] = None

# ── Sidebar ───────────────────────────────────────────────────────────────────
with st.sidebar:
    st.title("⚙️ Settings")

    st.subheader("🎙️ Transcription")
    whisper_model = st.selectbox(
        "Whisper model",
        ["tiny", "base", "small"],
        index=1,
        help="tiny=fastest | base=balanced ✅ | small=most accurate",
    )

    st.subheader("🎚️ Audio Mix")
    bgm_volume    = st.slider("Background music volume", 0.0, 1.0, 0.15, 0.05)
    speech_volume = st.slider("Hindi speech volume",     0.5, 2.0, 1.2,  0.1)

    st.subheader("ℹ️ Stack")
    st.caption("faster-whisper · Helsinki-NLP · gTTS · ffmpeg\n100% free · CPU · Open-source")

# ── Main ─────────────────────────────────────────────────────────────────────
st.title("🎬 Movie Dubber")
st.markdown("**Upload any video → Get Hindi dubbed output — 100% free**")

# STEP 1 — Upload
st.header("Step 1 — Upload Video")
uploaded = st.file_uploader(
    "MP4 / AVI / MKV / MOV (keep under ~200 MB for CPU tier)",
    type=["mp4", "avi", "mkv", "mov"],
)

if uploaded:
    video_path = os.path.join(tempfile.gettempdir(), uploaded.name)
    with open(video_path, "wb") as f:
        f.write(uploaded.read())

    st.video(video_path)
    st.success(f"✅ {uploaded.name} ({os.path.getsize(video_path)/1e6:.1f} MB)")

    # STEP 2 — Extract Audio
    st.header("Step 2 — Extract Audio")
    if st.button("🔊 Extract Audio", use_container_width=True):
        with st.spinner("Running ffmpeg…"):
            bar  = st.progress(0, "Extracting…")
            apth = extract_audio(video_path)
            bar.progress(70, "Detecting silence…")
            sil  = detect_silence_segments(apth)
            bar.progress(100, "Done!")
            st.session_state.audio_path = apth

        st.audio(apth)
        st.success("✅ Audio extracted!")
        if sil:
            with st.expander(f"🔇 {len(sil)} silence segment(s)"):
                for s, e in sil[:10]:
                    st.caption(f"  {s:.1f}s → {e:.1f}s")

    # STEP 3 — Transcribe
    if st.session_state.audio_path:
        st.header("Step 3 — Transcribe Speech")
        st.info(f"Whisper **{whisper_model}** on CPU — ~1–3 min/min of audio. ☕")

        if st.button("📝 Transcribe", use_container_width=True):
            bar  = st.progress(0, "Loading…")
            segs = transcribe_audio(
                st.session_state.audio_path,
                model_size=whisper_model,
                progress_cb=lambda p, t: bar.progress(p, t),
            )
            bar.progress(100, "Done!")
            st.session_state.segments = segs
            st.session_state.translated_segments = [{**s, "hindi_text": ""} for s in segs]
            st.session_state.speaker_voice_map = None  # reset on new transcription
            st.success(f"✅ {len(segs)} segments found!")

        # ── Download transcription as TXT ─────────────────────────────────────
        if st.session_state.segments:
            txt_content = build_transcription_txt(st.session_state.segments)
            st.download_button(
                label="⬇️ Download Transcription (.txt)",
                data=txt_content.encode("utf-8"),
                file_name="transcription.txt",
                mime="text/plain",
                use_container_width=True,
            )

        # ── Speaker voice assignment ──────────────────────────────────────────
        if st.session_state.segments:
            st.subheader("🎙️ Assign Voices to Speakers")
            st.caption(
                "Each detected speaker can have a different Hindi voice. "
                "Changes take effect when you generate Hindi speech."
            )

            # Find unique speaker IDs in order of first appearance
            seen, unique_speakers = set(), []
            for seg in st.session_state.segments:
                spk = str(seg.get("speaker", "1"))
                if spk not in seen:
                    seen.add(spk)
                    unique_speakers.append(spk)

            voice_names = list(VOICE_OPTIONS.keys())

            # Default assignments: alternate Male 1 / Female 1 for first two,
            # then Default for any extras
            defaults = ["Male 1 (Standard)", "Female 1 (UK)", "Male 2 (Indian)",
                        "Female 2 (Australia)", "Default"]

            voice_map = {}  # spk_id → tld string
            cols = st.columns(min(len(unique_speakers), 3))
            for idx, spk_id in enumerate(unique_speakers):
                col = cols[idx % len(cols)]
                with col:
                    default_voice = defaults[idx] if idx < len(defaults) else "Default"
                    chosen = st.selectbox(
                        f"Speaker {spk_id} voice",
                        voice_names,
                        index=voice_names.index(default_voice),
                        key=f"voice_spk_{spk_id}",
                    )
                    voice_map[spk_id] = VOICE_OPTIONS[chosen]

            if st.button("💾 Save Voice Assignments", use_container_width=True):
                st.session_state.speaker_voice_map = voice_map
                st.success(
                    "✅ Voice assignments saved! "
                    + " | ".join(
                        f"Speaker {k} → {[n for n,v in VOICE_OPTIONS.items() if v==tld][0]}"
                        for k, tld in voice_map.items()
                    )
                )

    # STEP 4 — Translate
    if st.session_state.segments:
        st.header("Step 4 — Translate to Hindi")

        tab_auto, tab_machine = st.tabs([
            "✍️ Write Hindi Yourself (Smart Assign)",
            "🤖 Auto Machine-Translate",
        ])

        # ── Tab A: Smart assign from user's own Hindi block ──────────────────
        with tab_auto:
            st.markdown(
                "Write your full Hindi dubbing script in **one box** — "
                "the app will split it and assign each line to the right speaker automatically."
            )
            st.markdown(
                "**Supported separators:** newline · Hindi danda `।` · `?` · `!`"
            )

            # Show original lines for reference
            with st.expander("📄 Show original transcribed lines for reference"):
                for seg in st.session_state.segments:
                    st.caption(
                        f"[{seg['start']:.1f}s → {seg['end']:.1f}s] "
                        f"Speaker {seg['speaker']}: {seg['text']}"
                    )

            hindi_block = st.text_area(
                "Write full Hindi dubbing text here",
                height=250,
                placeholder=(
                    "अरे सुनो, तुम कहाँ जा रहे हो?\n"
                    "मैं बाजार जा रहा हूँ।\n"
                    "ठीक है, जल्दी आना।"
                ),
                key="hindi_block_input",
            )

            n_segs  = len(st.session_state.segments)
            n_lines = len(_split_hindi_text(hindi_block)) if hindi_block.strip() else 0

            # Live line-count feedback
            if hindi_block.strip():
                if n_lines == n_segs:
                    st.success(f"✅ {n_lines} lines detected — perfect match with {n_segs} segments!")
                elif n_lines > n_segs:
                    st.warning(
                        f"⚠️ {n_lines} lines, {n_segs} segments — "
                        f"{n_lines - n_segs} extra line(s) will be merged automatically."
                    )
                else:
                    st.warning(
                        f"⚠️ {n_lines} lines, {n_segs} segments — "
                        f"{n_segs - n_lines} missing line(s) will be split automatically."
                    )

            if st.button("🎯 Auto Assign to Speakers", use_container_width=True,
                         disabled=not hindi_block.strip()):
                assigned = auto_assign_hindi_text(
                    st.session_state.segments, hindi_block
                )
                st.session_state.translated_segments = assigned
                st.success("✅ Hindi text assigned to all speakers!")

                # Preview result
                st.markdown("#### Assignment preview")
                for seg in assigned:
                    emotion = seg.get("emotion", "")
                    st.markdown(
                        f'<div class="seg-card">'
                        f'<span class="spk">Speaker {seg["speaker"]}</span> '
                        f'<span class="ts">{seg["start"]:.1f}s → {seg["end"]:.1f}s</span> '
                        f'<span style="font-size:0.8em;color:#aaa;">{emotion}</span><br>'
                        f'{seg["hindi_text"]}'
                        f'</div>',
                        unsafe_allow_html=True,
                    )

        # ── Tab B: Machine translation (original behaviour) ───────────────────
        with tab_machine:
            st.info(
                "Automatically translates each segment using Helsinki-NLP MarianMT. "
                "Free, offline, CPU-only."
            )
            if st.button("🌏 Auto Translate All Segments", use_container_width=True):
                segs = st.session_state.segments
                bar  = st.progress(0, "Loading translation model…")
                out  = []
                for i, seg in enumerate(segs):
                    bar.progress(int((i+1)/len(segs)*100), f"Translating {i+1}/{len(segs)}…")
                    out.append({**seg, "hindi_text": translate_to_hindi(
                        seg["text"], source_lang=seg.get("lang", "en")
                    )})
                    gc.collect()
                st.session_state.translated_segments = out
                st.success("✅ Translation done!")

    # STEP 5 — Edit
    if st.session_state.translated_segments and any(
        s.get("hindi_text") for s in st.session_state.translated_segments
    ):
        st.header("Step 5 — Review & Edit Translations")
        st.caption("Fix Hindi text before generating speech.")

        edited = []
        for i, seg in enumerate(st.session_state.translated_segments):
            c1, c2 = st.columns(2)
            with c1:
                st.markdown(
                    f'<div class="seg-card">'
                    f'<span class="spk">Speaker {seg.get("speaker","?")}</span> '
                    f'<span class="ts">{seg["start"]:.1f}s→{seg["end"]:.1f}s</span><br>'
                    f'<b>Original:</b> {seg["text"]}</div>',
                    unsafe_allow_html=True,
                )
            with c2:
                new = st.text_area(
                    f"Hindi #{i+1}", value=seg.get("hindi_text",""),
                    key=f"hi_{i}", height=100, label_visibility="collapsed",
                )
            edited.append({**seg, "hindi_text": new})

        if st.button("💾 Save Edits", use_container_width=True):
            st.session_state.translated_segments = edited
            st.success("✅ Saved!")

        # STEP 6 — TTS
        st.header("Step 6 — Generate Hindi Speech")
        if st.button("🎤 Generate Hindi Audio", use_container_width=True):
            bar  = st.progress(0, "Starting…")
            dpth = generate_hindi_speech(
                st.session_state.translated_segments,
                speaker="com",                                # global fallback tld
                speaker_voice_map=st.session_state.speaker_voice_map,
                speech_volume=speech_volume,
                progress_cb=lambda p, t: bar.progress(p, t),
            )
            bar.progress(100, "Done!")
            st.session_state.dubbed_audio_path = dpth
            st.audio(dpth)
            st.success("✅ Hindi audio ready!")

        # STEP 7 — Merge
        if st.session_state.dubbed_audio_path:
            st.header("Step 7 — Merge & Download")
            if st.button("🎬 Create Dubbed Video", use_container_width=True):
                with st.spinner("Merging…"):
                    out_path = merge_audio_video(
                        video_path=video_path,
                        dubbed_audio_path=st.session_state.dubbed_audio_path,
                        bgm_volume=bgm_volume,
                    )
                    st.session_state.output_video_path = out_path

                st.video(out_path)
                st.success("🎉 Done!")
                with open(out_path, "rb") as f:
                    st.download_button(
                        "⬇️ Download Hindi Dubbed Video",
                        data=f,
                        file_name="hindi_dubbed.mp4",
                        mime="video/mp4",
                        use_container_width=True,
                    )

else:
    st.markdown("""
    ---
    ### How it works

    | Step | What happens |
    |------|-------------|
    | 1️⃣ Upload | Any MP4 / AVI / MKV video |
    | 2️⃣ Extract | ffmpeg pulls the audio track |
    | 3️⃣ Transcribe | Whisper converts speech → text with timestamps |
    | 4️⃣ Translate | MarianMT translates → natural Hindi |
    | 5️⃣ Edit | Fix any translation manually |
    | 6️⃣ Speak | gTTS generates Hindi audio per segment |
    | 7️⃣ Merge | ffmpeg mixes Hindi audio back into video |

    > ⚠️ On CPU: transcription takes ~1–3 min per minute of video. Grab a chai ☕
    """)
