v3.6.0 同步发版
- 交互契约改对话式询问(禁点选工具、一次1~2问、先铺垫再问),规则库 3.6 - 盘面图默认生成+chart_shot 截图;品牌区、大运/流年小卡信息补全、交流群二维码卡 - day_boundary 子正换日;memory_save --update-chart 换盘留痕;share_export 事件简介+城市级地域 - share_push 失败诊断+代理引导;_runtime_guard;修 STAR_ORDER 三奇贵人 KeyError - README 规则计数 541→542;manifest 重建(1275 文件);测试 142 项全绿
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""HTML 盘面 → PNG 截图。
|
||||
|
||||
用法:
|
||||
python chart_shot.py <html路径> [-o 输出png路径]
|
||||
|
||||
截图分级回退:环境装有 playwright 则优先(file:// 全页截图,2 倍分辨率);
|
||||
否则用 macOS Chrome 无头截图(固定窗口,高度按 HTML 内容估算避免截断)。
|
||||
两者都不可用:退出码 2,stderr 提示直接打开 HTML。截图失败不影响排盘主流程。
|
||||
"""
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
import _runtime_guard # noqa: F401 import 即检查:Python < 3.10 时中文报错退出(码 3)
|
||||
|
||||
CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
|
||||
|
||||
def _shot_playwright(html: Path, out: Path) -> bool:
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
except ImportError:
|
||||
return False
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch()
|
||||
page = browser.new_page(device_scale_factor=2)
|
||||
page.goto(html.resolve().as_uri())
|
||||
page.screenshot(path=str(out), full_page=True)
|
||||
browser.close()
|
||||
except Exception:
|
||||
return False
|
||||
return out.is_file() and out.stat().st_size > 0
|
||||
|
||||
|
||||
def _content_height(html: Path) -> int:
|
||||
"""按 HTML 结构估算整页高度(px):头卡/主表/页脚取固定高,条带按格数折行。"""
|
||||
text = html.read_text(encoding="utf-8", errors="ignore")
|
||||
height = (900 + 40 * text.count('class="warn"')
|
||||
+ 150 * text.count('class="qrcard"'))
|
||||
for strip in text.split('class="strip"')[1:]:
|
||||
cells = strip.count('class="cell"')
|
||||
height += 36 + 132 * -(-cells // 9)
|
||||
return max(1300, min(height + 120, 4200))
|
||||
|
||||
|
||||
def _shot_chrome(html: Path, out: Path) -> bool:
|
||||
if not Path(CHROME).is_file():
|
||||
return False
|
||||
try:
|
||||
r = subprocess.run(
|
||||
[CHROME, "--headless=new", f"--screenshot={out.resolve()}",
|
||||
f"--window-size=1100,{_content_height(html)}",
|
||||
"--force-device-scale-factor=2", "--hide-scrollbars",
|
||||
html.resolve().as_uri()],
|
||||
capture_output=True, timeout=60)
|
||||
except Exception:
|
||||
return False
|
||||
return r.returncode == 0 and out.is_file() and out.stat().st_size > 0
|
||||
|
||||
|
||||
def shoot(html: Path, out: Path | None = None) -> Path | None:
|
||||
"""对 HTML 盘面截图,成功返回 PNG 绝对路径,无可用工具或失败返回 None。"""
|
||||
out = out or html.with_suffix(".png")
|
||||
if _shot_playwright(html, out) or _shot_chrome(html, out):
|
||||
return out.resolve()
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
prog="chart_shot.py",
|
||||
description="HTML 盘面 → PNG 截图(playwright 优先,Chrome 无头兜底)")
|
||||
ap.add_argument("html", help="HTML 盘面文件路径")
|
||||
ap.add_argument("-o", "--out", default=None,
|
||||
help="输出 PNG 路径(默认与 HTML 同目录同名 .png)")
|
||||
a = ap.parse_args()
|
||||
html = Path(a.html)
|
||||
if not html.is_file():
|
||||
print(f"ERROR: 找不到 HTML 文件 {html}", file=sys.stderr)
|
||||
return 2
|
||||
png = shoot(html, Path(a.out) if a.out else None)
|
||||
if png:
|
||||
print(png)
|
||||
return 0
|
||||
print("无可用截图工具(未检测到 playwright 或 Chrome),"
|
||||
"请直接打开 HTML 文件查看盘面。", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user