Files
2026-07-13 13:19:52 +08:00

217 lines
8.1 KiB
Python

"""M1-④ 自动归类
读取 data/styles_meta.json,按规则分到 30 大类,
'数字艺术' 进一步做二级细分;同时把 anime/manga 从插画拆出。
输出 data/catalog.json。
"""
import argparse
import json
import re
from collections import defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
# ---------- 顶级大类规则(注意顺序:先匹配的优先) ----------
# 形如 (中文名, English, [关键字...]),关键字会以 lowercase substring 匹配
PRIMARY_RULES: list[tuple[str, str, list[str]]] = [
# 二次元单独抽出,必须在"插画"之前
("二次元", "Anime/Manga",
["anime", "manga", "manhwa", "manhua", "chibi", "shojo", "shonen"]),
# 数字艺术(之后会再做二级细分)
("数字艺术", "Digital Art",
["digital", " 3d", "3d ", "cgi", "vector", "render"]),
# 插画
("插画", "Illustration",
["illustration", "illustrative", "comic ", "comics", "cartoon",
"storybook", "children's book"]),
# 图像小说(必须在插画之后才不被吃掉?其实顺序:插画里不含 graphic novel,所以放哪都行)
("图像小说", "Graphic Novel",
["graphic novel"]),
# 排版/字体
("排版字体", "Typography",
["typography", "typographic", "lettering", "calligraphic poster"]),
# 绘画
("绘画", "Painting",
["painting", "oil ", " oil", "acrylic", "gouache", "tempera"]),
# 水彩水墨
("水彩水墨", "Watercolor/Ink",
["watercolor", "watercolour", "ink ", " ink", "sumi", "wash painting",
"calligraph"]),
# 版画与素描
("版画素描", "Print/Sketch",
["lithograph", "etching", "engraving", "woodcut", "linocut", "sketch",
"charcoal", "pencil"]),
# 摄影
("摄影", "Photography",
["photo", "photograph", "cinematic", "film noir", "noir"]),
# 抽象
("抽象", "Abstract",
["abstract", "abstraction", "geometric abstraction", "minimalism",
"minimalist"]),
# 印象/后印象
("印象派", "Impressionism",
["impression", "post-impression", "pointillism"]),
# 表现/超现实
("表现超现实", "Surreal/Expressionism",
["expressionism", "expressive", "surreal", "dadaism", "psychedelic"]),
# 文艺复兴/古典
("古典", "Classical",
["renaissance", "baroque", "rococo", "neoclassic", "classical",
"romanticism", "chiaroscuro"]),
# 赛博朋克
("赛博朋克", "Cyberpunk",
["cyberpunk", "futuristic", "retro-futur", "sci-fi", "synthwave",
"vaporwave"]),
# 蒸汽朋克
("蒸汽朋克", "Steampunk", ["steampunk"]),
# 波普/海报
("波普海报", "Pop/Poster",
["pop ", "pop art", "poster", "propaganda", "graffiti", "street art"]),
# 装饰艺术
("装饰艺术", "Art Deco/Nouveau",
["deco", "nouveau", "arts and crafts"]),
# 民俗与文化
("民俗文化", "Folk/Cultural",
["folk", "tribal", "aboriginal", "ethnic", "ukiyo", "japanese ",
"chinese ", "indian ", "african", "celtic", "mexican"]),
# 幻想
("幻想", "Fantasy",
["fantasy", "mythic", "mythological", "fairy"]),
# 复古/做旧
("复古", "Vintage/Retro",
["vintage", "retro", "antique", "victorian", "edwardian", "19th",
"18th"]),
# 风景/自然
("风景自然", "Landscape/Nature",
["landscape", "botanical", "floral", "marine", "maritime", "seascape"]),
# 肖像
("肖像", "Portrait", ["portrait", "portraiture"]),
# 建筑/室内
("建筑室内", "Architecture",
["architectur", "interior", "urban", "cityscape"]),
# 时尚
("时尚", "Fashion", ["fashion", "editorial", "runway"]),
# 拼贴/混合
("拼贴混合", "Collage/Mixed",
["collage", "mixed media", "paper craft", "origami", "papercut"]),
# 雕塑
("雕塑", "Sculpture",
["sculpture", "bas-relief", "relief"]),
# 暗黑/哥特
("暗黑哥特", "Dark/Gothic",
["gothic", "dark ", "horror", "macabre", "dystop"]),
# 写实/学院派(新增)
("写实学院", "Realism/Academic",
["realism", "realist", "academic", "academicism", "regionalism"]),
# 现代/当代(新增)
("现代当代", "Modern/Contemporary",
["modernism", "modernist", "modern art", "contemporary"]),
]
# ---------- 数字艺术二级细分 ----------
DIGITAL_SUB_RULES: list[tuple[str, str, list[str]]] = [
("3D 动画/渲染", "3D Animation/Render",
["3d animation", "3d digital animation", "3d render", "3d rendering",
"3d model", "3d ", " 3d"]),
("CGI/电影渲染", "CGI/Cinematic Render",
["cgi", "cinematic", "render", "rendering"]),
("矢量/扁平", "Vector/Flat",
["vector", "flat ", "flat design"]),
("数字绘画", "Digital Painting",
["digital painting"]),
("数字插画", "Digital Illustration",
["digital illustration", "digital illustrative"]),
("数字艺术其它", "Other Digital Art", []), # 兜底
]
def classify(prefix: str) -> tuple[str, str, str | None, str | None]:
"""返回 (cn, en, sub_cn, sub_en)。未命中返回 ('其它','Other',None,None)。"""
p = (prefix or "").lower()
for cn, en, kws in PRIMARY_RULES:
if any(k in p for k in kws):
if cn == "数字艺术":
for scn, sen, skws in DIGITAL_SUB_RULES:
if skws and any(k in p for k in skws):
return cn, en, scn, sen
return cn, en, "数字艺术其它", "Other Digital Art"
return cn, en, None, None
return "其它", "Other", None, None
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--meta", default=str(ROOT / "data" / "styles_meta.json"))
ap.add_argument("--out", default=str(ROOT / "data" / "catalog.json"))
args = ap.parse_args()
meta_path = Path(args.meta)
out_path = Path(args.out)
if not meta_path.exists():
raise SystemExit(f"找不到 {meta_path}, 请先运行 build_dataset.py")
meta = json.loads(meta_path.read_text(encoding="utf-8"))
# 主类 -> 子类 -> [style_key,...]
catalog: dict[str, dict] = {}
cat_count: dict[str, int] = defaultdict(int)
sub_count: dict[str, int] = defaultdict(int)
for key, info in meta.items():
prefix = info.get("style_prefix") or info.get("style_full") or key
cn, en, scn, sen = classify(prefix)
info["category"] = cn
info["category_en"] = en
info["sub_category"] = scn
info["sub_category_en"] = sen
cat_count[cn] += 1
bucket = catalog.setdefault(cn, {
"name_cn": cn,
"name_en": en,
"count": 0,
"subs": {},
"items": [], # 没有子类的直接平铺
})
bucket["count"] += 1
if scn:
sub = bucket["subs"].setdefault(scn, {
"name_cn": scn,
"name_en": sen,
"count": 0,
"items": [],
})
sub["count"] += 1
sub["items"].append(key)
sub_count[f"{cn}/{scn}"] += 1
else:
bucket["items"].append(key)
# 写回 styles_meta.json (附加 category 字段)
meta_path.write_text(
json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8"
)
# 写出 catalog.json
out_path.write_text(
json.dumps(catalog, ensure_ascii=False, indent=2), encoding="utf-8"
)
# 控制台输出统计
print(f"{'大类':<24}{'子类':<22}{'风格数':>8}")
print("-" * 56)
for cn, info in sorted(catalog.items(), key=lambda x: -x[1]["count"]):
if info["subs"]:
for scn, sub in sorted(info["subs"].items(),
key=lambda x: -x[1]["count"]):
print(f"{cn:<24}{scn:<22}{sub['count']:>8}")
else:
print(f"{cn:<24}{'':<22}{info['count']:>8}")
print("-" * 56)
total = sum(info["count"] for info in catalog.values())
print(f"{'合计':<46}{total:>8}")
print(f"\n[完成] catalog.json -> {out_path}")
print(f"[完成] meta 已附加 category 字段 -> {meta_path}")
if __name__ == "__main__":
main()