#!/usr/bin/env python3
"""
Subtitle Renderer · 字幕渲染引擎
=============================
将 SRT 字幕文件渲染为 PNG 序列，再通过 FFmpeg 合成到视频中。

依赖：
  pip install Pillow pysrt

用法：
  # 基本用法（SRT → PNG 序列）
  python subtitle-renderer.py --srt input.srt --output-dir ./subtitles-png/

  # 指定视频尺寸（PNG 宽度匹配视频）
  python subtitle-renderer.py --srt input.srt --output-dir ./subtitles-png/ --width 1080 --height 1920

  # 渲染后直接合成到视频
  python subtitle-renderer.py --srt input.srt --video input.mp4 --output output.mp4

  # 自定义字幕样式
  python subtitle-renderer.py --srt input.srt --font-size 48 --font-color white --style clean-white --position bottom

  # 作为模块导入
  from subtitle_renderer import render_subtitles
  render_subtitles("input.srt", "./subtitles-png/")

字幕样式配置：
  --font-size    : 字体大小（默认 36）
  --font-color   : 字体颜色（默认 white）
  --style        : 字幕样式（reference-drama / short-drama-bold / clean-white / black-box，默认 reference-drama）
  --position     : 位置（top / middle / bottom，默认 bottom）
  --margin-bottom : 底部边距（默认 100px）

路径：
  video-ai-system/engines/subtitle-renderer.py
"""

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

try:
    import pysrt
except ImportError:
    print("[ERROR] 缺少依赖：pysrt")
    print("请先安装：pip install pysrt")
    sys.exit(1)

try:
    from PIL import Image, ImageDraw, ImageFont
except ImportError:
    print("[ERROR] 缺少依赖：Pillow")
    print("请先安装：pip install Pillow")
    sys.exit(1)


# 默认字幕样式
DEFAULT_STYLE = {
    "font_size": 38,
    "font_color": "white",
    "bg_enabled": False,
    "position": "bottom",  # top / middle / bottom
    "margin_bottom": 28,
    "margin_horizontal": 60,
    "stroke_color": "black",
    "stroke_width": 1,
    "bold_weight": 2,
    "video_width": 1080,
    "video_height": 1920,
}

SUBTITLE_STYLE_PRESETS = {
    "reference-drama": {
        "font_color": (255, 255, 255, 255),
        "bg_enabled": False,
        "stroke_width": 2,
        "stroke_color": (0, 0, 0, 235),
        "bold_weight": 1,
        "line_box_ratio": 1.45,
    },
    "short-drama-bold": {
        "font_color": (255, 255, 255, 255),
        "bg_enabled": False,
        "stroke_width": 1,
        "stroke_color": (18, 18, 18, 220),
        "bold_weight": 2,
        "line_box_ratio": 1.55,
    },
    "clean-white": {
        "font_color": (255, 255, 255, 255),
        "bg_enabled": False,
        "stroke_width": 0,
        "bold_weight": 1,
        "line_box_ratio": 1.55,
    },
    "black-box": {
        "font_color": (255, 255, 255, 255),
        "bg_enabled": True,
        "bg_fill": (0, 0, 0, 160),
        "stroke_width": 0,
        "bold_weight": 1,
        "line_box_ratio": 1.55,
    },
    "outlined-white": {
        "font_color": (255, 255, 255, 255),
        "bg_enabled": False,
        "stroke_width": 2,
        "stroke_color": (0, 0, 0, 255),
        "bold_weight": 1,
        "line_box_ratio": 1.55,
    },
}

CHINESE_FONT_CANDIDATES = [
    "/System/Library/Fonts/PingFang.ttc",
    "/System/Library/Fonts/Hiragino Sans GB.ttc",
    "/System/Library/Fonts/STHeiti Medium.ttc",
    "/System/Library/Fonts/Supplemental/Songti.ttc",
    "/System/Library/Fonts/Supplemental/Kaiti.ttc",
    "/Library/Fonts/Arial Unicode.ttf",
    "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
]


def load_font(font_size: int):
    for font_path in CHINESE_FONT_CANDIDATES:
        if not os.path.isfile(font_path):
            continue
        try:
            return ImageFont.truetype(font_path, font_size)
        except Exception:
            continue
    return ImageFont.load_default()


def render_subtitle_png(
    text: str,
    output_path: str,
    width: int = 1080,
    height: int = 200,
    style: dict = None
) -> bool:
    """
    渲染单条字幕为 PNG（带背景）

    :param text: 字幕文本
    :param output_path: 输出 PNG 路径
    :param width: PNG 宽度（匹配视频宽度）
    :param height: PNG 高度
    :param style: 字幕样式字典
    :return: 是否成功
    """
    if style is None:
        style = DEFAULT_STYLE

    try:
        # 创建透明背景 PNG
        img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
        draw = ImageDraw.Draw(img)

        # 字体（使用系统字体）
        font_size = style.get("font_size", 36)
        font = load_font(font_size)

        # 计算文本尺寸
        bbox = draw.textbbox((0, 0), text, font=font)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]

        # 居中位置
        x = (width - text_width) // 2
        y = (height - text_height) // 2

        # 可选背景。默认纯白字幕不画背景。
        if style.get("bg_enabled", False):
            bg_padding = 20
            bg_x1 = x - bg_padding
            bg_y1 = y - bg_padding
            bg_x2 = x + text_width + bg_padding
            bg_y2 = y + text_height + bg_padding
            draw.rectangle([bg_x1, bg_y1, bg_x2, bg_y2], fill=style.get("bg_fill", (0, 0, 0, 160)))

        # 可选描边。短剧默认使用极细暗边，不使用投影。
        stroke_width = style.get("stroke_width", 2)
        stroke_color = style.get("stroke_color", "black")
        if stroke_width > 0:
            for offset in range(-stroke_width, stroke_width + 1):
                draw.text((x + offset, y), text, font=font, fill=stroke_color)
                draw.text((x, y + offset), text, font=font, fill=stroke_color)

        # 绘制主文本。bold_weight 用多次微偏移模拟加粗，避免依赖某台机器是否有粗体中文字体。
        font_color = style.get("font_color", "white")
        bold_weight = max(1, int(style.get("bold_weight", 1)))
        offsets = [(0, 0)]
        if bold_weight >= 2:
            offsets += [(-1, 0), (1, 0), (0, -1), (0, 1)]
        if bold_weight >= 3:
            offsets += [(-1, -1), (1, -1), (-1, 1), (1, 1)]
        for dx, dy in offsets:
            draw.text((x + dx, y + dy), text, font=font, fill=font_color)

        # 保存
        os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
        img.save(output_path, "PNG")

        return True

    except Exception as e:
        print(f"[ERROR] 渲染字幕失败：{e}")
        return False


def render_subtitles(
    srt_path: str,
    output_dir: str,
    style: dict = None
) -> dict:
    """
    渲染 SRT 字幕为 PNG 序列

    :param srt_path: SRT 文件路径
    :param output_dir: 输出目录
    :param style: 字幕样式字典
    :return: {idx: {"png": png_path, "start": start_time, "end": end_time, "text": text}}
    """
    if not os.path.isfile(srt_path):
        print(f"[ERROR] SRT 文件不存在：{srt_path}")
        return {}

    os.makedirs(output_dir, exist_ok=True)

    # 加载 SRT
    subs = pysrt.open(srt_path, encoding="utf-8")
    print(f"[INFO] 找到 {len(subs)} 条字幕，开始渲染 PNG 序列...")

    results = {}
    for sub in subs:
        idx = str(sub.index).zfill(4)
        text = sub.text.strip()
        start_time = sub.start.ordinal  # 毫秒
        end_time = sub.end.ordinal

        # 渲染 PNG
        png_path = os.path.join(output_dir, f"{idx}.png")
        video_width = style.get("video_width", 1080) if style else 1080
        font_size = style.get("font_size", 36) if style else 36
        line_box_ratio = style.get("line_box_ratio", 1.55) if style else 1.55
        png_height = int(font_size * line_box_ratio)

        ok = render_subtitle_png(text, png_path, width=video_width, height=png_height, style=style)
        if ok:
            results[idx] = {
                "png": png_path,
                "start": start_time,
                "end": end_time,
                "text": text
            }

    print(f"[OK] 字幕 PNG 序列渲染完成：{len(results)}/{len(subs)} 成功")
    return results


def burn_subtitles_to_video(
    video_path: str,
    subtitles: dict,
    output_path: str,
    video_width: int = 1080,
    video_height: int = 1920
) -> bool:
    """
    将 PNG 字幕序列合成到视频中（使用 FFmpeg overlay 滤镜）

    :param video_path: 输入视频路径
    :param subtitles: render_subtitles 返回的字典
    :param output_path: 输出视频路径
    :param video_width: 视频宽度
    :param video_height: 视频高度
    :return: 是否成功
    """
    if not subtitles:
        print("[ERROR] 没有字幕数据")
        return False

    if not os.path.isfile(video_path):
        print(f"[ERROR] 视频文件不存在：{video_path}")
        return False

    os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)

    ordered_subs = [subtitles[k] for k in sorted(subtitles.keys())]
    cmd = ["ffmpeg", "-y", "-i", video_path]
    for sub in ordered_subs:
        cmd.extend(["-i", sub["png"]])

    margin_bottom = DEFAULT_STYLE["margin_bottom"]
    chains = []
    previous = "[0:v]"

    for i, sub in enumerate(ordered_subs, start=1):
        start_sec = sub["start"] / 1000.0
        end_sec = sub["end"] / 1000.0
        scaled = f"[s{i}]"
        out = f"[v{i}]"
        chains.append(f"[{i}:v]scale={video_width}:-1{scaled}")
        chains.append(
            f"{previous}{scaled}"
            f"overlay=x=0:y=H-h-{margin_bottom}:"
            f"enable='between(t,{start_sec:.3f},{end_sec:.3f})'{out}"
        )
        previous = out

    filter_complex = ";".join(chains)
    cmd.extend([
        "-filter_complex", filter_complex,
        "-map", previous,
        "-map", "0:a?",
        "-c:v", "libx264",
        "-pix_fmt", "yuv420p",
        "-c:a", "copy",
        "-shortest",
        output_path,
    ])

    print("[INFO] 开始合成字幕到视频...")
    try:
        result = subprocess.run(cmd, check=False, capture_output=True, text=True)
    except FileNotFoundError:
        print("[ERROR] 未找到 ffmpeg")
        return False

    if result.returncode != 0:
        print("[ERROR] FFmpeg 合成失败")
        print(result.stderr[-2000:])
        return False

    if os.path.isfile(output_path) and os.path.getsize(output_path) > 0:
        print(f"[OK] 字幕已合成到视频：{output_path}")
        return True

    print(f"[ERROR] 输出视频未生成：{output_path}")
    return False


def main():
    parser = argparse.ArgumentParser(
        description="Subtitle Renderer · 字幕渲染引擎",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例：
  python subtitle-renderer.py --srt input.srt --output-dir ./subtitles-png/
  python subtitle-renderer.py --srt input.srt --video input.mp4 --output output.mp4
  python subtitle-renderer.py --srt input.srt --font-size 48 --position bottom
"""
    )
    parser.add_argument("--srt", required=True, help="SRT 字幕文件路径")
    parser.add_argument("--output-dir", help="PNG 序列输出目录")
    parser.add_argument("--video", help="输入视频路径（可选，用于直接合成）")
    parser.add_argument("--output", help="输出视频路径（配合 --video 使用）")
    parser.add_argument("--font-size", type=int, default=36, help="字体大小")
    parser.add_argument("--font-color", default="white", help="字体颜色")
    parser.add_argument("--style", default="reference-drama", choices=sorted(SUBTITLE_STYLE_PRESETS.keys()), help="字幕样式")
    parser.add_argument("--margin-bottom", type=int, default=None, help="字幕距离画面底部的像素边距")
    parser.add_argument("--position", default="bottom", choices=["top", "middle", "bottom"], help="字幕位置")
    parser.add_argument("--video-width", type=int, default=1080, help="视频宽度")
    parser.add_argument("--video-height", type=int, default=1920, help="视频高度")

    args = parser.parse_args()

    # 构建样式字典
    style = {
        **SUBTITLE_STYLE_PRESETS[args.style],
        "font_size": args.font_size,
        "font_color": args.font_color,
        "position": args.position,
        "video_width": args.video_width,
        "video_height": args.video_height,
    }
    if args.margin_bottom is not None:
        style["margin_bottom"] = args.margin_bottom

    # 渲染 PNG 序列
    output_dir = args.output_dir or "./subtitles-png/"
    subtitles = render_subtitles(args.srt, output_dir, style)

    if not subtitles:
        sys.exit(1)

    # 如果指定了视频，则合成
    if args.video and args.output:
        ok = burn_subtitles_to_video(args.video, subtitles, args.output, args.video_width, args.video_height)
        if not ok:
            sys.exit(1)

    print(f"\n[OK] 字幕 PNG 序列已生成：{output_dir}")
    print(f"[INFO] 共 {len(subtitles)} 条字幕")
    sys.exit(0)


if __name__ == "__main__":
    main()
