VanguardReMaster/tools/import_card_art.py

163 lines
5.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Скачать арты триальной деки с Cardfight!! Vanguard Wiki.
Пример:
python tools/import_card_art.py \
--url "https://cardfight.fandom.com/wiki/Trial_Deck_1:_Blaster_Blade" \
--output client/Assets/Vanguard/Resources/CardArt
Fandom иногда отвечает Cloudflare 403 на запросы не из браузера. В этом
случае сохраните HTML/API-ответ страницы из браузера и передайте:
python tools/import_card_art.py --html saved.html --output ...
"""
from __future__ import annotations
import argparse
import html
import json
import re
import sys
import urllib.parse
import urllib.request
from pathlib import Path
def fetch(url: str) -> str:
request = urllib.request.Request(
url,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/126 Safari/537.36",
"Accept": "text/html,application/json;q=0.9,*/*;q=0.8",
},
)
with urllib.request.urlopen(request, timeout=60) as response:
return response.read().decode("utf-8", errors="replace")
def page_html(url: str, local_html: str | None) -> str:
if local_html:
raw = Path(local_html).read_text(encoding="utf-8", errors="replace")
if raw.lstrip().startswith("{"):
payload = json.loads(raw)
return payload["parse"]["text"]
return raw
# Пытаемся через API: он отдаёт HTML статьи без рекламной обвязки.
page = url.split("/wiki/", 1)[-1]
api = (
"https://cardfight.fandom.com/api.php?action=parse&format=json"
"&formatversion=2&prop=text&page=" + urllib.parse.quote(page)
)
raw = fetch(api)
payload = json.loads(raw)
if "error" in payload:
raise RuntimeError(payload["error"].get("info", "Wiki API error"))
return payload["parse"]["text"]
def set_gallery_links(page: str) -> list[str]:
links = re.findall(r'href="(/wiki/Set_Gallery:[^"]+)"', page)
return list(dict.fromkeys("https://cardfight.fandom.com" + link for link in links))
def image_candidates(html_text: str, code: str) -> list[str]:
"""Ищем картинку, у которой alt начинается с кода принта."""
pattern = re.compile(r"<img\b[^>]*>", re.I)
result: list[tuple[int, str]] = []
for tag in pattern.findall(html_text):
alt_match = re.search(r'\balt="([^"]*)"', tag, re.I)
src_match = re.search(r'\bsrc="([^"]*)"', tag, re.I)
if not alt_match or not src_match:
continue
alt = html.unescape(alt_match.group(1))
src = html.unescape(src_match.group(1))
if not alt.startswith(code) or not src.startswith("https://static.wikia.nocookie.net/"):
continue
score = 0
if "Sample" in alt:
score += 10
if "Holo" in alt or "Parallel" in alt:
score += 5
result.append((score, src))
result.sort(key=lambda item: item[0])
urls = []
for _, src in result:
# Берём оригинал, а не превью 126px.
src = re.sub(r"/scale-to-width-down/\d+", "", src)
src = src.split("?")[0] + "?format=original"
if src not in urls:
urls.append(src)
return urls
def save_image(url: str, output: Path, image_key: str) -> None:
extension = ".png" if ".png" in url.lower() else ".jpg"
target = output / (image_key + extension)
if target.exists():
return
request = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(request, timeout=60) as response:
data = response.read()
target.write_bytes(data)
print(f" {target.name} ({len(data) // 1024} KiB)")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--url", required=False, help="Страница триал-деки")
parser.add_argument("--html", help="Сохранённый HTML/API-ответ страницы")
parser.add_argument("--output", required=True, help="Папка CardArt")
args = parser.parse_args()
if not args.url and not args.html:
parser.error("нужен --url или --html")
output = Path(args.output)
output.mkdir(parents=True, exist_ok=True)
source_url = args.url or "https://cardfight.fandom.com/wiki/"
page = page_html(source_url, args.html)
galleries = set_gallery_links(page)
if not galleries:
print("Не нашёл ссылку Set_Gallery на странице.", file=sys.stderr)
return 1
# Английские VGE-галереи приоритетнее, чтобы не перезаписать арт
# японской/тайский версией.
galleries.sort(key=lambda value: 0 if "/VGE-" in value else 1 if "/VG-" in value else 2)
downloaded = 0
for gallery in galleries:
try:
gallery_html = page_html(gallery, None)
except Exception as exc: # noqa: BLE001
print(f"Пропуск {gallery}: {exc}", file=sys.stderr)
continue
# Коды вида TD01/001EN, alt картинки: "TD01/001EN Name".
codes = sorted(set(re.findall(r"\b(TD\d{2}/\d{3})(?:EN)?\b", gallery_html)))
for code in codes:
urls = image_candidates(gallery_html, code)
if not urls:
continue
image_key = code.lower().replace("/", "_")
try:
save_image(urls[0], output, image_key)
downloaded += 1
except Exception as exc: # noqa: BLE001
print(f"Ошибка {code}: {exc}", file=sys.stderr)
print(f"Готово: обработано карт {downloaded}.")
return 0
if __name__ == "__main__":
raise SystemExit(main())