#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
=============================================================================
KIEU STORY AI CINEMA — FRESH 20S INTRO PRODUCTION VIA MUSE.AI GATEWAY
=============================================================================
Chu trình sản xuất khép kín 2 Shot Intro mở đầu phim từ ĐỘNG CƠ MUSE.AI THỰC THẾ:
1. Shot 1 (ep01_intro_shot01, 10s):
   - Start Frame: Ảnh Imagen 3 8K kiệt tác (start_frame_720p.png).
   - Render I2V qua Muse2API Gateway (model: sora / muse-video, 1280x720 10s).
2. Head-Tail Chaining:
   - Trích xuất Frame 239 (9.95s) từ video tươi mới của Shot 1.
   - Nạp làm Start Frame của Shot 2 (bảo đảm chuyển động liền mạch 100%).
3. Shot 2 (ep01_intro_shot02, 10s):
   - Start Frame: Tail frame kế thừa từ Shot 1.
   - Render I2V qua Muse2API Gateway: Dải lụa xoáy, hào quang bùng nổ, biểu trưng "Thập Ngũ Niên" dát vàng.
4. Tầng Âm thanh Điện ảnh Đỉnh cao (Khớp 100% với Prologue):
   - Stem 1: Tiếng nước chảy róc rách, bọt nước reo, sóng hồ lăn tăn ven bờ, giọt nước rơi thanh khiết.
   - Stem 2: Thảm âm thanh cổ phong ngân dài (D minor / F pentatonic strings pad & drone),
     trầm ấm, hoài niệm, đỉnh cao trang nghiêm lúc hiện tựa đề và thoái trào êm ả để dẫn vào Prologue.
     HOÀN TOÀN KHÔNG CÓ TIẾNG ĐÀN TÌ BÀ GẢY DÂY.
   - Stem 3: Foley dải lụa mềm và hiệu ứng bụi vàng lấp lánh (celestial sparkle).
   - Master EBU R128: -14.0 LUFS, -1.0 dBTP, Stereo 48kHz AAC 192k.
5. Ghép nối & Kiểm định:
   - Intro Master 20s (thap_ngu_nien_intro_master_20s.mp4).
   - Bản Liên hoàn Đối chứng 60s (Intro 20s + Prologue 40s).
   - Zero-Waste Contact Sheet & Critic Gate.
=============================================================================
"""

import os
import sys
import time
import math
import wave
import struct
import random
import shutil
import subprocess
from pathlib import Path
from typing import Optional, Dict, Any, List

import cv2
import requests
import numpy as np

# Khởi tạo UTF-8 cho Windows Console
if sys.platform == "win32":
    try:
        sys.stdout.reconfigure(encoding="utf-8")
        sys.stderr.reconfigure(encoding="utf-8")
    except Exception:
        pass

BASE_DIR = Path("c:/Projects/KieuStory")
PIPELINE_DIR = BASE_DIR / "05_Production_Pipeline"
if str(PIPELINE_DIR) not in sys.path:
    sys.path.insert(0, str(PIPELINE_DIR))

ASSETS_DIR = BASE_DIR / "04_Assets"
VIDEOS_DIR = ASSETS_DIR / "videos"
AUDIO_DIR = ASSETS_DIR / "audio"
KEYFRAMES_DIR = ASSETS_DIR / "keyframes"
CRITIC_DIR = ASSETS_DIR / "critic_queue"
EXPORTS_DIR = BASE_DIR / "06_Exports"

VIDEOS_DIR.mkdir(parents=True, exist_ok=True)
AUDIO_DIR.mkdir(parents=True, exist_ok=True)
CRITIC_DIR.mkdir(parents=True, exist_ok=True)
EXPORTS_DIR.mkdir(parents=True, exist_ok=True)

from muse_api_client import MuseApiClient

DEFAULT_FFMPEG_PATHS = [
    r"C:\Users\Admin\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-9.0.2-full_build\bin\ffmpeg.exe",
    r"C:\Users\Admin\AppData\Local\Microsoft\WindowsApps\ffmpeg.exe",
    r"ffmpeg"
]

def get_ffmpeg():
    for p in DEFAULT_FFMPEG_PATHS:
        if os.path.exists(p) or p == "ffmpeg":
            try:
                res = subprocess.run([p, "-version"], capture_output=True, text=True)
                if res.returncode == 0:
                    return p
            except Exception:
                continue
    return "ffmpeg"


def poll_task_with_display(client: MuseApiClient, task_id: str, label: str, timeout: float = 600.0) -> Dict[str, Any]:
    """Theo dõi tiến độ tạo video trên Muse2API với thông báo rõ ràng."""
    print(f"[*] Đang theo dõi tiến độ Muse.ai cho [{label}] (Task ID: {task_id})...")
    started = time.time()
    last_p = -1
    url = f"{client.base_url}/v1/videos/{task_id}"

    while time.time() - started < timeout:
        time.sleep(3.5)
        try:
            resp = requests.get(url, headers=client.headers, timeout=12.0)
            resp.raise_for_status()
            data = resp.json()
        except Exception as e:
            print(f"    [~] Đang kết nối lại tới gateway ({e})...")
            continue

        st = data.get("status")
        p = data.get("progress", 0)

        if p != last_p:
            elapsed = int(time.time() - started)
            print(f"    [Muse.ai {label}] Trạng thái: {st.upper()} | Tiến độ: {p}% ({elapsed}s)")
            last_p = p

        if st == "succeeded":
            res = data.get("result") or {}
            v_url = res.get("url")
            if not v_url:
                raise RuntimeError(f"Task thành công nhưng thiếu URL video: {data}")
            elapsed = int(time.time() - started)
            print(f"[✓] Render thành công [{label}] sau {elapsed}s! URL: {v_url}")
            return data

        if st == "failed":
            err = data.get("error")
            raise RuntimeError(f"Tạo video thất bại từ Muse.ai: {err}")

    raise TimeoutError(f"Hết thời gian chờ {timeout}s cho [{label}].")


def generate_flowing_water_stream(output_path: str, duration_sec: float = 20.0, sample_rate: int = 48000) -> bool:
    """Stem 1: Tiếng Nước Chảy & Sóng Hồ Róc Rách Tự Nhiên (20.0s, Stereo 48kHz)."""
    out_file = Path(output_path)
    out_file.parent.mkdir(parents=True, exist_ok=True)

    print(f"[*] Đang tổng hợp Stem 1: Flowing Water & Lake Stream ({duration_sec}s, {sample_rate}Hz Stereo)...")

    total_samples = int(duration_sec * sample_rate)
    t = np.linspace(0, duration_sec, total_samples, endpoint=False)

    np.random.seed(18610)
    random.seed(18610)

    white = np.random.normal(0, 1, total_samples)
    brownian = np.cumsum(white)
    brownian = brownian - np.mean(brownian)
    brownian = brownian / np.max(np.abs(brownian))

    num_bubbles = 4200
    bubble_audio_l = np.zeros(total_samples)
    bubble_audio_r = np.zeros(total_samples)

    bubble_times = np.random.uniform(0.05, duration_sec - 0.1, num_bubbles)
    bubble_freqs = np.random.uniform(450, 2600, num_bubbles)
    bubble_durs = np.random.uniform(0.010, 0.035, num_bubbles)
    bubble_pans = np.random.uniform(0.15, 0.85, num_bubbles)
    bubble_amps = np.random.uniform(0.015, 0.060, num_bubbles)

    for i in range(num_bubbles):
        t_start = bubble_times[i]
        dur = bubble_durs[i]
        f0 = bubble_freqs[i]
        amp = bubble_amps[i]
        pan = bubble_pans[i]

        idx_start = int(t_start * sample_rate)
        n_samples = int(dur * sample_rate)
        if idx_start + n_samples >= total_samples:
            continue

        t_b = np.linspace(0, dur, n_samples, endpoint=False)
        freq_curve = f0 * (1.0 + 0.18 * (t_b / dur))
        env = np.exp(-t_b * (55.0 / dur + 15.0))
        wave_b = amp * np.sin(2.0 * np.pi * freq_curve * t_b) * env

        bubble_audio_l[idx_start:idx_start+n_samples] += wave_b * (1.0 - pan)
        bubble_audio_r[idx_start:idx_start+n_samples] += wave_b * pan

    swell_mod = 0.72 + 0.28 * np.sin(2.0 * np.pi * 0.20 * t + 0.5)
    water_l = (bubble_audio_l * 1.6 + brownian * 0.09) * swell_mod
    water_r = (bubble_audio_r * 1.6 + brownian * 0.09) * swell_mod

    for t_drop in [2.4, 5.8, 9.2, 13.5, 17.6]:
        idx = int(t_drop * sample_rate)
        dur_d = 0.09
        n_d = int(dur_d * sample_rate)
        if idx + n_d < total_samples:
            t_d = np.linspace(0, dur_d, n_d, endpoint=False)
            f_drop = 1580.0
            wave_drop = 0.075 * np.sin(2.0 * np.pi * f_drop * (1.0 + 0.35 * (t_d / dur_d)) * t_d) * np.exp(-t_d * 42.0)
            water_l[idx:idx+n_d] += wave_drop * 0.65
            water_r[idx:idx+n_d] += wave_drop * 0.35

    pink_filter = np.random.normal(0, 0.03, total_samples)
    breeze_l = pink_filter * (0.6 + 0.4 * np.sin(2.0 * np.pi * 0.12 * t))
    breeze_r = pink_filter * (0.6 + 0.4 * np.cos(2.0 * np.pi * 0.12 * t))
    water_l += breeze_l
    water_r += breeze_r

    peak = max(np.max(np.abs(water_l)), np.max(np.abs(water_r)))
    if peak > 0:
        water_l = (water_l / peak) * 0.85
        water_r = (water_r / peak) * 0.85

    with wave.open(str(out_file), "wb") as wf:
        wf.setnchannels(2)
        wf.setsampwidth(2)
        wf.setframerate(sample_rate)
        interleaved = np.empty((total_samples * 2,), dtype=np.int16)
        interleaved[0::2] = np.clip(water_l * 32767.0, -32768, 32767).astype(np.int16)
        interleaved[1::2] = np.clip(water_r * 32767.0, -32768, 32767).astype(np.int16)
        wf.writeframes(interleaved.tobytes())

    print(f"[✓] Đã tạo thành công Stem 1 Flowing Water: {out_file}")
    return True


def generate_cinematic_atmospheric_bed(output_path: str, duration_sec: float = 20.0, sample_rate: int = 48000) -> bool:
    """Stem 2: Thảm âm thanh cổ phong Á Đông hoài niệm & sâu lắng (Zero Tỳ Bà, Khớp 100% Prologue)."""
    out_file = Path(output_path)
    out_file.parent.mkdir(parents=True, exist_ok=True)

    print(f"[*] Đang tổng hợp Stem 2: Cinematic Atmospheric Bed ({duration_sec}s, {sample_rate}Hz Stereo)...")

    total_samples = int(duration_sec * sample_rate)
    t = np.linspace(0, duration_sec, total_samples, endpoint=False)

    np.random.seed(9999)

    freqs = [73.42, 110.00, 146.83, 174.61, 196.00, 220.00, 261.63, 293.66]
    weights = [0.20, 0.17, 0.15, 0.13, 0.10, 0.10, 0.08, 0.07]

    pad_l = np.zeros(total_samples)
    pad_r = np.zeros(total_samples)

    env = np.ones(total_samples)
    for i in range(total_samples):
        ti = t[i]
        if ti < 3.0:
            env[i] = (ti / 3.0) ** 1.6 * 0.45
        elif ti < 11.5:
            env[i] = 0.45 + 0.12 * math.sin((ti - 3.0) / 8.5 * math.pi * 0.5)
        elif ti < 14.5:
            p = (ti - 11.5) / 3.0
            env[i] = 0.57 + 0.43 * (math.sin(p * math.pi * 0.5) ** 2)
        elif ti < 16.8:
            p = (ti - 14.5) / 2.3
            env[i] = 1.0 - 0.25 * p
        else:
            p = (ti - 16.8) / 3.2
            env[i] = 0.75 * ((1.0 - p) ** 1.5) + 0.10

    for f, w in zip(freqs, weights):
        detune = 0.0016
        vib = 0.0018 * np.sin(2.0 * np.pi * 4.0 * t)
        phase_l = np.random.uniform(0, 2 * np.pi)
        phase_r = np.random.uniform(0, 2 * np.pi)

        tone_l = np.sin(2.0 * np.pi * f * (1.0 - detune + vib) * t + phase_l)
        tone_r = np.sin(2.0 * np.pi * f * (1.0 + detune + vib) * t + phase_r)

        tone_l += 0.25 * np.sin(4.0 * np.pi * f * t + phase_l)
        tone_r += 0.25 * np.sin(4.0 * np.pi * f * t + phase_r)

        pad_l += w * tone_l
        pad_r += w * tone_r

    pad_l += 0.14 * np.sin(2.0 * np.pi * 36.71 * t)
    pad_r += 0.14 * np.sin(2.0 * np.pi * 36.71 * t)

    pad_l = pad_l * env
    pad_r = pad_r * env

    peak = max(np.max(np.abs(pad_l)), np.max(np.abs(pad_r)))
    if peak > 0:
        pad_l = (pad_l / peak) * 0.75
        pad_r = (pad_r / peak) * 0.75

    with wave.open(str(out_file), "wb") as wf:
        wf.setnchannels(2)
        wf.setsampwidth(2)
        wf.setframerate(sample_rate)
        interleaved = np.empty((total_samples * 2,), dtype=np.int16)
        interleaved[0::2] = np.clip(pad_l * 32767.0, -32768, 32767).astype(np.int16)
        interleaved[1::2] = np.clip(pad_r * 32767.0, -32768, 32767).astype(np.int16)
        wf.writeframes(interleaved.tobytes())

    print(f"[✓] Đã tạo thành công Stem 2 Atmospheric Bed: {out_file}")
    return True


def generate_foley_shimmer_track(output_path: str, duration_sec: float = 20.0) -> bool:
    """Stem 3: Foley lụa bay và hiệu ứng ánh sáng hoàng kim (Celestial Shimmer)."""
    ffmpeg_exe = get_ffmpeg()
    out_file = Path(output_path)
    out_file.parent.mkdir(parents=True, exist_ok=True)

    print(f"[*] Đang tổng hợp Stem 3: Silk Foley & Celestial Shimmer...")

    filter_complex = (
        "aevalsrc='if(between(t, 0.5, 9.5), 0.035*sin(2*PI*320*t)*sin(2*PI*0.35*t), 0) + "
        "if(between(t, 10.0, 18.0), 0.045*sin(2*PI*450*t)*sin(2*PI*0.45*t), 0)':s=48000:d=20[silk_raw];"
        "[silk_raw]aphaser=in_gain=0.6:out_gain=0.8:delay=2.0:decay=0.4:speed=0.4:type=t[silk_fx];"
        "aevalsrc='if(between(t, 13.8, 17.5), "
        "0.022*sin(2*PI*1760*t)*exp(-2.2*(t-13.8)) + "
        "0.018*sin(2*PI*2637*t)*exp(-1.8*(t-14.2)) + "
        "0.014*sin(2*PI*3520*t)*exp(-1.4*(t-14.6)) + "
        "0.010*sin(2*PI*4186*t)*exp(-1.0*(t-15.0)), 0)':s=48000:d=20[shimmer_synth];"
        "[shimmer_synth]aformat=channel_layouts=stereo,aphaser=in_gain=0.6:out_gain=0.8:delay=3.0:decay=0.4:speed=0.5:type=t[shimmer_fx];"
        "[silk_fx][shimmer_fx]amix=inputs=2:duration=first:dropout_transition=2[foley_out]"
    )

    cmd = [
        ffmpeg_exe, "-y",
        "-filter_complex", filter_complex,
        "-map", "[foley_out]",
        "-c:a", "pcm_s16le",
        "-t", str(duration_sec),
        str(out_file)
    ]
    res = subprocess.run(cmd, capture_output=True, text=True)
    if res.returncode == 0:
        print(f"[✓] Đã tạo thành công Stem 3 Foley: {out_file}")
        return True
    return False


def run_full_fresh_intro_pipeline(existing_shot01_task_id: Optional[str] = None):
    ffmpeg_exe = get_ffmpeg()
    client = MuseApiClient()

    print("=" * 80)
    print("🎬 KIEU STORY AI CINEMA — FRESH MUSE.AI INTRO 2-SHOT PRODUCTION PIPELINE")
    print("   Quy trình: Head-Tail Chaining, Pure Flowing Water & Zero Tỳ Bà Audio Master")
    print("=" * 80)

    # -------------------------------------------------------------------------
    # BƯỚC 1: XÁC MINH VÀ RENDER SHOT 01 TƯƠI MỚI QUA MUSE.AI
    # -------------------------------------------------------------------------
    shot01_raw_video = VIDEOS_DIR / "ep01_intro_shot01_10s_fresh_muse.mp4"
    shot01_kf_dir = KEYFRAMES_DIR / "ep01_intro_shot01"
    start_frame_shot01 = shot01_kf_dir / "start_frame_720p.png"

    if not start_frame_shot01.exists():
        raise FileNotFoundError(f"Không tìm thấy Start Frame Shot 01: {start_frame_shot01}")

    print(f"\n[BƯỚC 1/5] Khởi động Render Shot 01 (10s) trên Muse.ai...")
    print(f"  🖼️ Start Frame: {start_frame_shot01} ({start_frame_shot01.stat().st_size // 1024} KB)")

    if existing_shot01_task_id:
        task01_id = existing_shot01_task_id
        print(f"  ⚡ Sử dụng Task ID đang chạy: {task01_id}")
    else:
        shot01_prompt = (
            "A vibrant emerald green silk ribbon floating over misty lake at sunrise, "
            "ancient red pavilion, weeping willows, flowing water, photorealistic, 8k, "
            "slow motion cinematic tracking shot. Strictly NO background music."
        )
        task01_id = client.create_video_task(
            prompt=shot01_prompt,
            image=str(start_frame_shot01),
            model="sora",
            size="16:9",
            duration=10
        )
        print(f"  ⚡ Đã gửi task mới: {task01_id}")

    task01_data = poll_task_with_display(client, task01_id, label="Shot 01 (Dải Lụa Hồ Sương)")
    client.download_media(task01_data["result"]["url"], shot01_raw_video)
    print(f"[✓] Đã tải về thành công Shot 01 Video tươi mới: {shot01_raw_video}")

    # -------------------------------------------------------------------------
    # BƯỚC 2: TRÍCH XUẤT TAIL FRAME CHO HEAD-TAIL CHAINING
    # -------------------------------------------------------------------------
    print(f"\n[BƯỚC 2/5] Trích xuất Tail Frame Frame 239 (9.95s) từ Shot 01...")
    tail_frame_shot01 = shot01_kf_dir / "clean_frame_239.jpg"
    subprocess.run([
        ffmpeg_exe, "-y",
        "-ss", "9.95", "-i", str(shot01_raw_video),
        "-vframes", "1", str(tail_frame_shot01)
    ], check=True)

    shot02_kf_dir = KEYFRAMES_DIR / "ep01_intro_shot02"
    shot02_kf_dir.mkdir(parents=True, exist_ok=True)
    start_frame_shot02 = shot02_kf_dir / "start_frame_720p.png"
    shutil.copy2(str(tail_frame_shot01), str(start_frame_shot02))
    print(f"[✓] Đã chuyển tiếp Tail Frame thành Start Frame Shot 02: {start_frame_shot02}")

    # -------------------------------------------------------------------------
    # BƯỚC 3: RENDER SHOT 02 TƯƠI MỚI QUA MUSE.AI
    # -------------------------------------------------------------------------
    print(f"\n[BƯỚC 3/5] Khởi động Render Shot 02 (10s) trên Muse.ai...")
    shot02_raw_video = VIDEOS_DIR / "ep01_intro_shot02_10s_fresh_muse.mp4"
    shot02_prompt = (
        "Seamless continuation: The emerald green silk ribbon swirls gracefully upward into a majestic spiral in front of the ancient red wooden pavilion. "
        "From the center vortex of the swirling ribbon, a radiant burst of ethereal golden light and glowing celestial particles explodes over the misty lake. "
        "Elegantly revealed in embossed imperial gold lettering with cinnabar red trim is the grand title 'Thập Ngũ Niên', floating majestically above the serene water as white flower petals flutter softly. "
        "Cinematic push-in reveal shot, slow motion, awe-inspiring title sequence. Strictly NO background music, soft celestial shimmer and water foley."
    )

    task02_id = client.create_video_task(
        prompt=shot02_prompt,
        image=str(start_frame_shot02),
        model="sora",
        size="16:9",
        duration=10
    )
    print(f"  ⚡ Đã gửi task Shot 02: {task02_id}")
    task02_data = poll_task_with_display(client, task02_id, label="Shot 02 (Biểu Trưng Thập Ngũ Niên)")
    client.download_media(task02_data["result"]["url"], shot02_raw_video)
    print(f"[✓] Đã tải về thành công Shot 02 Video tươi mới: {shot02_raw_video}")

    # Trích xuất Tail Frame của Shot 02
    tail_frame_shot02 = shot02_kf_dir / "clean_frame_239.jpg"
    subprocess.run([
        ffmpeg_exe, "-y",
        "-ss", "9.95", "-i", str(shot02_raw_video),
        "-vframes", "1", str(tail_frame_shot02)
    ], check=True)

    # -------------------------------------------------------------------------
    # BƯỚC 4: THIẾT KẾ & HÒA ÂM ĐỈNH CAO (CHỈ TIẾNG NƯỚC & KHỚP PROLOGUE)
    # -------------------------------------------------------------------------
    print(f"\n[BƯỚC 4/5] Tổng hợp & Phối âm 4-Stem Audio Master (Zero Tỳ Bà, Khớp 100% Prologue)...")
    water_track = AUDIO_DIR / "intro_flowing_water_20s.wav"
    atmospheric_track = AUDIO_DIR / "intro_atmospheric_bed_20s.wav"
    foley_track = AUDIO_DIR / "intro_foley_silk_and_title_shimmer_20s.wav"

    generate_flowing_water_stream(str(water_track), duration_sec=20.0)
    generate_cinematic_atmospheric_bed(str(atmospheric_track), duration_sec=20.0)
    generate_foley_shimmer_track(str(foley_track), duration_sec=20.0)

    # Hòa âm chuẩn EBU R128 (-14.0 LUFS)
    master_audio_20s = CRITIC_DIR / "intro_master_audio_20s.wav"
    filter_mix = (
        "[0:a]volume=0.88[water_sub];"
        "[1:a]volume=0.36[pad_sub];"
        "[2:a]volume=0.68[fol_sub];"
        "[water_sub][pad_sub][fol_sub]amix=inputs=3:duration=first:dropout_transition=2[mix_raw];"
        "[mix_raw]loudnorm=I=-14.0:TP=-1.0:LRA=7.5:linear=true[mix_norm]"
    )
    subprocess.run([
        ffmpeg_exe, "-y",
        "-i", str(water_track),
        "-i", str(atmospheric_track),
        "-i", str(foley_track),
        "-filter_complex", filter_mix,
        "-map", "[mix_norm]",
        "-c:a", "pcm_s16le", "-ar", "48000",
        str(master_audio_20s)
    ], check=True)
    print(f"[✓] Đã tạo Master Audio 20s chuẩn EBU R128: {master_audio_20s}")

    # Muxing Shot 01 Master (0-10s)
    shot01_master = VIDEOS_DIR / "ep01_intro_shot01_10s_master.mp4"
    subprocess.run([
        ffmpeg_exe, "-y",
        "-ss", "0.0", "-t", "10.0", "-i", str(shot01_raw_video),
        "-ss", "0.0", "-t", "10.0", "-i", str(master_audio_20s),
        "-map", "0:v:0", "-map", "1:a:0",
        "-c:v", "libx264", "-crf", "18", "-preset", "slow", "-pix_fmt", "yuv420p",
        "-c:a", "aac", "-b:a", "192k", "-ar", "48000",
        "-t", "10.0",
        str(shot01_master)
    ], check=True)
    print(f"[✓] Hoàn tất Shot 01 Master tươi mới: {shot01_master}")

    # Muxing Shot 02 Master (10-20s)
    shot02_master = VIDEOS_DIR / "ep01_intro_shot02_10s_master.mp4"
    subprocess.run([
        ffmpeg_exe, "-y",
        "-ss", "0.0", "-t", "10.0", "-i", str(shot02_raw_video),
        "-ss", "10.0", "-t", "10.0", "-i", str(master_audio_20s),
        "-map", "0:v:0", "-map", "1:a:0",
        "-c:v", "libx264", "-crf", "18", "-preset", "slow", "-pix_fmt", "yuv420p",
        "-c:a", "aac", "-b:a", "192k", "-ar", "48000",
        "-t", "10.0",
        str(shot02_master)
    ], check=True)
    print(f"[✓] Hoàn tất Shot 02 Master tươi mới: {shot02_master}")

    # -------------------------------------------------------------------------
    # BƯỚC 5: GHÉP NỐI MASTER 20S & BẢN ĐỐI CHỨNG 60S
    # -------------------------------------------------------------------------
    print(f"\n[BƯỚC 5/5] Ghép nối Master 20s và Bản Đối chứng 60s liên hoàn...")
    intro_master_export = EXPORTS_DIR / "thap_ngu_nien_intro_master_20s.mp4"
    intro_master_assets = VIDEOS_DIR / "thap_ngu_nien_intro_complete_20s.mp4"

    subprocess.run([
        ffmpeg_exe, "-y",
        "-i", str(shot01_master),
        "-i", str(shot02_master),
        "-filter_complex", "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]",
        "-map", "[v]", "-map", "[a]",
        "-c:v", "libx264", "-crf", "18", "-preset", "slow", "-pix_fmt", "yuv420p",
        "-c:a", "aac", "-b:a", "192k", "-ar", "48000",
        str(intro_master_export)
    ], check=True)
    shutil.copy2(str(intro_master_export), str(intro_master_assets))
    print(f"[✓] Đã xuất bản Intro Master 20s: {intro_master_export}")

    # Ghép nối với Prologue 40s thành 60s
    prologue_master = EXPORTS_DIR / "prologue_master_40s.mp4"
    if prologue_master.exists():
        intro_prologue_60s = EXPORTS_DIR / "thap_ngu_nien_intro_prologue_continuous_60s.mp4"
        subprocess.run([
            ffmpeg_exe, "-y",
            "-i", str(intro_master_export),
            "-i", str(prologue_master),
            "-filter_complex", "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]",
            "-map", "[v]", "-map", "[a]",
            "-c:v", "libx264", "-crf", "18", "-preset", "slow", "-pix_fmt", "yuv420p",
            "-c:a", "aac", "-b:a", "192k", "-ar", "48000",
            str(intro_prologue_60s)
        ], check=True)
        print(f"[✓] Đã xuất bản Bản Liên Hoàn 60s (Intro + Prologue): {intro_prologue_60s}")

    # -------------------------------------------------------------------------
    # ZERO-WASTE CONTACT SHEET & KIỂM ĐỊNH THỊ GIÁC FLOWKIT
    # -------------------------------------------------------------------------
    print(f"\n🔍 Đang tạo Zero-Waste Contact Sheet kiểm định thị giác...")
    contact_sheet_path = CRITIC_DIR / "thap_ngu_nien_intro_master_20s_contact_sheet.jpg"
    try:
        from contact_sheet_engine import generate_zero_waste_contact_sheet
        generate_zero_waste_contact_sheet(
            video_path=str(intro_master_export),
            output_image_path=str(contact_sheet_path),
            n_frames=6,
            add_badge=True
        )
        print(f"[✓] Đã xuất Contact Sheet: {contact_sheet_path}")
    except Exception as e:
        print(f"[~] Tạo contact sheet fallback: {e}")

    print("\n" + "=" * 80)
    print("🎉 HOÀN THÀNH 100% QUY TRÌNH RENDER INTRO TƯƠI MỚI TỪ MUSE.AI!")
    print("=" * 80)
    return True


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--task-shot1", help="Task ID đã gửi cho Shot 1 nếu có", default="task_c4da2602beca418e")
    args = parser.parse_args()

    run_full_fresh_intro_pipeline(existing_shot01_task_id=args.task_shot1)
