文件预览

check_subtitle_style.py

查看 YouTube Chinese Subtitle Burn-in 技能包中的文件内容。

文件内容

scripts/check_subtitle_style.py

#!/usr/bin/env python3
from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

from subtitle_style import STYLE_VERSION, get_style, style_names


STYLE_RE = re.compile(r"^Style:\s*([^,]+),(.*)$")
FIELD_NAMES = [
    "Fontname",
    "Fontsize",
    "PrimaryColour",
    "SecondaryColour",
    "OutlineColour",
    "BackColour",
    "Bold",
    "Italic",
    "Underline",
    "StrikeOut",
    "ScaleX",
    "ScaleY",
    "Spacing",
    "Angle",
    "BorderStyle",
    "Outline",
    "Shadow",
    "Alignment",
    "MarginL",
    "MarginR",
    "MarginV",
    "Encoding",
]


def parse_styles(path: Path) -> dict[str, dict[str, str]]:
    styles: dict[str, dict[str, str]] = {}
    for line in path.read_text(encoding="utf-8-sig").splitlines():
        match = STYLE_RE.match(line)
        if not match:
            continue
        name = match.group(1).strip()
        values = [value.strip() for value in match.group(2).split(",")]
        if len(values) != len(FIELD_NAMES):
            raise SystemExit(f"FAIL: malformed style line for {name}: expected {len(FIELD_NAMES)} fields, got {len(values)}")
        styles[name] = dict(zip(FIELD_NAMES, values))
    return styles


def expected(profile: str, style_name: str) -> dict[str, str]:
    style = get_style(profile)
    english = style_name.lower().startswith("english")
    return {
        "Fontname": style.font,
        "Fontsize": str(style.en_size if english else style.zh_size),
        "Bold": "0" if english else "-1",
        "Outline": str(max(3, style.outline - 1) if english else style.outline),
        "Shadow": str(style.shadow),
        "MarginL": "80" if english else str(style.margin_l),
        "MarginR": "80" if english else str(style.margin_r),
        "MarginV": str(style.en_margin_v if english else style.zh_margin_v),
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Check ASS subtitle styles against fixed skill profiles.")
    parser.add_argument("ass", type=Path)
    parser.add_argument("--style-profile", choices=style_names(), required=True)
    parser.add_argument("--require-version-comment", action="store_true")
    args = parser.parse_args()

    if not args.ass.exists():
        print(f"FAIL: missing ASS file {args.ass}")
        return 1
    text = args.ass.read_text(encoding="utf-8-sig")
    if args.require_version_comment and f"StyleVersion: {STYLE_VERSION}" not in text:
        print(f"FAIL: missing style version comment {STYLE_VERSION}")
        return 1

    styles = parse_styles(args.ass)
    names = ["Default"] if args.style_profile.startswith("zh-only") else ["Chinese", "English"]
    issues: list[str] = []
    for name in names:
        if name not in styles:
            issues.append(f"missing style {name}")
            continue
        for field, value in expected(args.style_profile, name).items():
            actual = styles[name].get(field)
            if actual != value:
                issues.append(f"{name}.{field}: expected {value}, got {actual}")
    if issues:
        print(f"FAIL: {len(issues)} subtitle style issue(s)")
        for issue in issues:
            print(f"- {issue}")
        return 1
    print(f"PASS: {args.ass} matches {args.style_profile}")
    return 0


if __name__ == "__main__":
    sys.exit(main())