#!/usr/bin/env python3
"""
Reference Subtitle Analyzer · 样片字幕量化分析器
==============================================
从样片截图里量字幕：字高、底部距离、描边宽度、字幕框位置、字号比例。

依赖：
  pip install opencv-python pillow numpy

用法：
  # 分析单张截图
  python reference-subtitle-analyzer.py --image reference-frame.png --output analysis.json

  # 批量分析样片截图
  python reference-subtitle-analyzer.py --batch ./reference-frames/ --output batch-analysis.json

  # 交互式标注模式（手动框选字幕区域）
  python reference-subtitle-analyzer.py --image reference-frame.png --interactive

输出 JSON 格式：
  {
    "video_height": 1920,
    "video_width": 1080,
    "subtitle_box": {
      "x": 100,
      "y": 1750,
      "width": 880,
      "height": 120
    },
    "subtitle_height_px": 120,
    "bottom_distance_px": 50,
    "bottom_distance_ratio": 0.026,
    "font_size_ratio": 0.0625,
    "stroke_width_px": 2,
    "alignment": "center",
    "margin_horizontal_px": 100
  }

路径：
  video-ai-system/engines/subtitle-pipeline/reference-analysis/reference-subtitle-analyzer.py
"""

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

try:
    import cv2
    import numpy as np
    from PIL import Image
except ImportError as e:
    print(f"[ERROR] 缺少依赖：{e}")
    print("请先安装：pip install opencv-python pillow numpy")
    sys.exit(1)


def analyze_subtitle_region(image_path: str, interactive: bool = False) -> dict:
    """
    分析字幕区域，量化字幕参数
    
    :param image_path: 截图路径
    :param interactive: 是否交互式标注（手动框选字幕区域）
    :return: 字幕参数字典
    """
    # 读取图片
    img = cv2.imread(image_path)
    if img is None:
        print(f"[ERROR] 无法读取图片：{image_path}")
        return {}
    
    height, width = img.shape[:2]
    print(f"[INFO] 图片尺寸：{width}x{height}")
    
    if interactive:
        # 交互式标注模式：手动框选字幕区域
        print("[INFO] 交互式标注模式：请在弹出的窗口中用鼠标框选字幕区域")
        print("[INFO] 框选完成后按空格或回车确认，按ESC取消")
        
        roi = cv2.selectROI("Select Subtitle Region", img, showCrosshair=True)
        cv2.destroyAllWindows()
        
        x, y, w, h = int(roi[0]), int(roi[1]), int(roi[2]), int(roi[3])
        if w == 0 or h == 0:
            print("[ERROR] 未框选字幕区域")
            return {}
    else:
        # 自动检测字幕区域（假设字幕在底部）
        # 策略：检测底部区域的文本（通过边缘检测 + 轮廓查找）
        print("[INFO] 自动检测字幕区域...")
        
        # 1. 裁剪底部区域（假设字幕在底部 15% 区域）
        bottom_region = img[int(height * 0.85):, :]
        
        # 2. 转为灰度图
        gray = cv2.cvtColor(bottom_region, cv2.COLOR_BGR2GRAY)
        
        # 3. 二值化（检测白色文本）
        _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
        
        # 4. 查找轮廓
        contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        if not contours:
            print("[WARN] 未检测到字幕区域，使用默认底部区域")
            # 使用默认底部区域
            x = int(width * 0.1)
            y = int(height * 0.85)
            w = int(width * 0.8)
            h = int(height * 0.12)
        else:
            # 合并所有轮廓的边界框
            # 正确算式：找到所有轮廓的最小外接矩形
            all_x = []
            all_y = []
            all_w = []
            all_h = []
            
            for cnt in contours:
                cx, cy, cw, ch = cv2.boundingRect(cnt)
                all_x.append(cx)
                all_y.append(cy)
                all_w.append(cw)
                all_h.append(ch)
            
            # 正确合并：先在 bottom_region 坐标系里算完，最后再加偏移
            x_bottom = min(all_x)
            y_bottom = min(all_y)
            max_x_bottom = max([all_x[i] + all_w[i] for i in range(len(all_x))])
            max_y_bottom = max([all_y[i] + all_h[i] for i in range(len(all_y))])
            w = max_x_bottom - x_bottom
            h = max_y_bottom - y_bottom

            # 扩展边界框（包含描边）
            padding = 10
            x = max(0, x_bottom - padding)
            y = max(0, y_bottom - padding) + int(height * 0.85)  # 偏移只加一次，在最后
            w = min(width - x, w + 2 * padding)
            h = min(height - y, h + 2 * padding)
    
    # 计算字幕参数
    # 先做边界校验，防止裁剪出空数组
    x = max(0, min(x, width - 1))
    y = max(0, min(y, height - 1))
    w = max(1, min(w, width - x))
    h = max(1, min(h, height - y))
    
    subtitle_box = {"x": x, "y": y, "width": w, "height": h}
    subtitle_height_px = h
    bottom_distance_px = height - (y + h)
    bottom_distance_ratio = bottom_distance_px / height
    font_size_ratio = h / height
    
    # 估算描边宽度（通过检测文本边缘的黑色像素）
    stroke_width_px = estimate_stroke_width(img, x, y, w, h)
    
    # 判断对齐方式（居中/左对齐/右对齐）
    alignment = "center" if abs(x + w/2 - width/2) < width * 0.1 else "left" if x < width * 0.3 else "right"
    
    # 计算水平边距
    margin_horizontal_px = x
    
    result = {
        "image_path": image_path,
        "video_width": width,
        "video_height": height,
        "subtitle_box": subtitle_box,
        "subtitle_height_px": subtitle_height_px,
        "bottom_distance_px": bottom_distance_px,
        "bottom_distance_ratio": round(bottom_distance_ratio, 4),
        "font_size_ratio": round(font_size_ratio, 4),
        "stroke_width_px": stroke_width_px,
        "alignment": alignment,
        "margin_horizontal_px": margin_horizontal_px
    }
    
    print(f"[OK] 字幕参数已量化：")
    print(f"  字幕框：x={x}, y={y}, w={w}, h={h}")
    print(f"  字高：{subtitle_height_px}px")
    print(f"  底部距离：{bottom_distance_px}px ({bottom_distance_ratio*100:.1f}%)")
    print(f"  字号比例：{font_size_ratio*100:.1f}%")
    print(f"  描边宽度：{stroke_width_px}px")
    print(f"  对齐方式：{alignment}")
    
    return result


def estimate_stroke_width(img: np.ndarray, x: int, y: int, w: int, h: int) -> int:
    """
    估算描边宽度（通过检测文本边缘的黑色像素）
    
    :param img: 图片数组
    :param x: 字幕框 x 坐标
    :param y: 字幕框 y 坐标
    :param w: 字幕框宽度
    :param h: 字幕框高度
    :return: 估算的描边宽度（像素）
    """
    # 裁剪字幕区域
    subtitle_region = img[y:y+h, x:x+w]
    
    # 转为灰度图
    gray = cv2.cvtColor(subtitle_region, cv2.COLOR_BGR2GRAY)
    
    # 检测边缘（Canny）
    edges = cv2.Canny(gray, 100, 200)
    
    # 统计边缘像素到文本区域的距离（估算描边宽度）
    # 简化方案：假设描边是黑色，检测白色文本周围的黑色像素环
    _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
    
    # 形态学操作：膨胀（模拟描边）
    kernel = np.ones((3, 3), np.uint8)
    dilated = cv2.dilate(binary, kernel, iterations=1)
    
    # 计算膨胀后的边缘宽度
    edge_width = cv2.absdiff(dilated, binary)
    stroke_pixels = cv2.countNonZero(edge_width)
    
    # 估算平均描边宽度
    if stroke_pixels > 0:
        # 简化：假设描边宽度是 1-3px
        return 2  # 默认值，实际应通过更精确的算法计算
    else:
        return 0


def batch_analyze(images_dir: str) -> dict:
    """
    批量分析样片截图
    
    :param images_dir: 截图目录
    :return: 批量分析结果
    """
    results = {}
    image_extensions = [".jpg", ".jpeg", ".png", ".bmp"]
    
    for file in os.listdir(images_dir):
        if any(file.lower().endswith(ext) for ext in image_extensions):
            image_path = os.path.join(images_dir, file)
            print(f"\n[INFO] 分析图片：{file}")
            result = analyze_subtitle_region(image_path)
            if result:
                results[file] = result
    
    # 计算平均值
    if results:
        avg_result = calculate_average(results)
        results["_average"] = avg_result
    
    return results


def calculate_average(results: dict) -> dict:
    """
    计算批量分析结果的平均值
    
    :param results: 批量分析结果
    :return: 平均值字典
    """
    keys = ["subtitle_height_px", "bottom_distance_px", "bottom_distance_ratio", 
            "font_size_ratio", "stroke_width_px", "margin_horizontal_px"]
    
    avg = {}
    for key in keys:
        values = [r[key] for r in results.values() if key in r]
        if values:
            avg[key] = round(sum(values) / len(values), 2)
    
    avg["alignment"] = max(set(r["alignment"] for r in results.values()), 
                          key=lambda x: sum(1 for r in results.values() if r["alignment"] == x))
    
    return avg


def main():
    parser = argparse.ArgumentParser(
        description="Reference Subtitle Analyzer · 样片字幕量化分析器",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例：
  python reference-subtitle-analyzer.py --image reference-frame.png --output analysis.json
  python reference-subtitle-analyzer.py --batch ./reference-frames/ --output batch-analysis.json
  python reference-subtitle-analyzer.py --image reference-frame.png --interactive
"""
    )
    parser.add_argument("--image", help="单张截图路径")
    parser.add_argument("--batch", help="批量分析截图目录")
    parser.add_argument("--output", required=True, help="输出 JSON 文件路径")
    parser.add_argument("--interactive", action="store_true", help="交互式标注模式")
    
    args = parser.parse_args()
    
    if args.image:
        result = analyze_subtitle_region(args.image, args.interactive)
        if not result:
            sys.exit(1)
        
        with open(args.output, "w", encoding="utf-8") as f:
            json.dump(result, f, ensure_ascii=False, indent=2)
        
        print(f"\n[OK] 分析结果已保存：{args.output}")
    
    elif args.batch:
        results = batch_analyze(args.batch)
        
        with open(args.output, "w", encoding="utf-8") as f:
            json.dump(results, f, ensure_ascii=False, indent=2)
        
        print(f"\n[OK] 批量分析结果已保存：{args.output}")
        if "_average" in results:
            print(f"[INFO] 平均字幕参数：{results['_average']}")
    
    else:
        print("[ERROR] 请指定 --image 或 --batch")
        sys.exit(1)


if __name__ == "__main__":
    main()
