|
@@ -34,6 +34,7 @@ from urllib.parse import urlparse, parse_qs, unquote
|
|
|
import pipeline
|
|
import pipeline
|
|
|
import exporter
|
|
import exporter
|
|
|
import slot_workflow
|
|
import slot_workflow
|
|
|
|
|
+import spine_builder
|
|
|
import providers
|
|
import providers
|
|
|
import config
|
|
import config
|
|
|
|
|
|
|
@@ -100,7 +101,104 @@ def _load_library(game):
|
|
|
if not os.path.isfile(library_path):
|
|
if not os.path.isfile(library_path):
|
|
|
return {"game": game, "characters": [], "vfx": [], "ui": [], "ui_art": []}
|
|
return {"game": game, "characters": [], "vfx": [], "ui": [], "ui_art": []}
|
|
|
with open(library_path, encoding="utf-8") as f:
|
|
with open(library_path, encoding="utf-8") as f:
|
|
|
- return json.load(f)
|
|
|
|
|
|
|
+ lib = json.load(f)
|
|
|
|
|
+ if _repair_library_index(game, lib):
|
|
|
|
|
+ with open(library_path, "w", encoding="utf-8") as f:
|
|
|
|
|
+ json.dump(lib, f, ensure_ascii=False, indent=2)
|
|
|
|
|
+ return lib
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _spine_size_from_json(path):
|
|
|
|
|
+ try:
|
|
|
|
|
+ data = json.load(open(path, encoding="utf-8"))
|
|
|
|
|
+ sk = data.get("skeleton", {})
|
|
|
|
|
+ return int(round(sk.get("width") or 1000)), int(round(sk.get("height") or 1000))
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return 1000, 1000
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _image_size(path):
|
|
|
|
|
+ try:
|
|
|
|
|
+ from PIL import Image
|
|
|
|
|
+ with Image.open(path) as img:
|
|
|
|
|
+ return img.size
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ return 0, 0
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _repair_library_index(game, lib):
|
|
|
|
|
+ """Index files that exist on disk but were not written to library.json.
|
|
|
|
|
+
|
|
|
|
|
+ This lets partial retry jobs become visible immediately even if a later
|
|
|
|
|
+ required task failed before the old library index was updated.
|
|
|
|
|
+ """
|
|
|
|
|
+ changed = False
|
|
|
|
|
+ base = os.path.join(OUT_ROOT, game)
|
|
|
|
|
+ manifest = _manifest_from_library(lib)
|
|
|
|
|
+ by_section = {
|
|
|
|
|
+ "characters": {x.get("id"): x for x in lib.get("characters", [])},
|
|
|
|
|
+ "ui_art": {x.get("id"): x for x in lib.get("ui_art", [])},
|
|
|
|
|
+ "vfx": {x.get("id"): x for x in lib.get("vfx", [])},
|
|
|
|
|
+ "ui": {x.get("id"): x for x in lib.get("ui", [])},
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for c in manifest.get("characters", []):
|
|
|
|
|
+ cid = c.get("id")
|
|
|
|
|
+ if not cid or cid in by_section["characters"]:
|
|
|
|
|
+ continue
|
|
|
|
|
+ paths = [os.path.join(base, "characters", f"{cid}.{ext}") for ext in ("json", "atlas", "png")]
|
|
|
|
|
+ if all(os.path.isfile(p) for p in paths):
|
|
|
|
|
+ w, h = _spine_size_from_json(paths[0])
|
|
|
|
|
+ item = {
|
|
|
|
|
+ "id": cid,
|
|
|
|
|
+ "png": f"characters/{cid}.png",
|
|
|
|
|
+ "w": w, "h": h,
|
|
|
|
|
+ "type": c.get("type", "spine"),
|
|
|
|
|
+ "role": c.get("role", ""),
|
|
|
|
|
+ "animations": spine_builder.anim_data(c.get("animations", ["idle"])),
|
|
|
|
|
+ "files": [f"characters/{cid}.json", f"characters/{cid}.atlas", f"characters/{cid}.png"],
|
|
|
|
|
+ }
|
|
|
|
|
+ lib.setdefault("characters", []).append(item)
|
|
|
|
|
+ by_section["characters"][cid] = item
|
|
|
|
|
+ changed = True
|
|
|
|
|
+
|
|
|
|
|
+ for a in manifest.get("ui_art", []):
|
|
|
|
|
+ aid = a.get("id")
|
|
|
|
|
+ if not aid or aid in by_section["ui_art"]:
|
|
|
|
|
+ continue
|
|
|
|
|
+ path = os.path.join(base, "ui_art", f"{aid}.png")
|
|
|
|
|
+ if os.path.isfile(path):
|
|
|
|
|
+ w, h = _image_size(path)
|
|
|
|
|
+ item = {"id": aid, "file": f"ui_art/{aid}.png", "w": w, "h": h,
|
|
|
|
|
+ "transparent": a.get("transparent", True)}
|
|
|
|
|
+ lib.setdefault("ui_art", []).append(item)
|
|
|
|
|
+ by_section["ui_art"][aid] = item
|
|
|
|
|
+ changed = True
|
|
|
|
|
+
|
|
|
|
|
+ for v in manifest.get("vfx", []):
|
|
|
|
|
+ vid = v.get("id")
|
|
|
|
|
+ if not vid or vid in by_section["vfx"]:
|
|
|
|
|
+ continue
|
|
|
|
|
+ path = os.path.join(base, "vfx", f"{vid}.particle.json")
|
|
|
|
|
+ if os.path.isfile(path):
|
|
|
|
|
+ cfg = json.load(open(path, encoding="utf-8"))
|
|
|
|
|
+ item = {"id": vid, "template": v.get("template"),
|
|
|
|
|
+ "file": f"vfx/{vid}.particle.json", "config": cfg}
|
|
|
|
|
+ lib.setdefault("vfx", []).append(item)
|
|
|
|
|
+ by_section["vfx"][vid] = item
|
|
|
|
|
+ changed = True
|
|
|
|
|
+
|
|
|
|
|
+ ui_path = os.path.join(base, "ui", "TweenPresets.ts")
|
|
|
|
|
+ if os.path.isfile(ui_path):
|
|
|
|
|
+ for u in manifest.get("ui", []):
|
|
|
|
|
+ uid = u.get("id")
|
|
|
|
|
+ if uid and uid not in by_section["ui"]:
|
|
|
|
|
+ item = {"id": uid, "preset": u.get("preset"), "params": u.get("params", {})}
|
|
|
|
|
+ lib.setdefault("ui", []).append(item)
|
|
|
|
|
+ by_section["ui"][uid] = item
|
|
|
|
|
+ changed = True
|
|
|
|
|
+
|
|
|
|
|
+ return changed
|
|
|
|
|
|
|
|
|
|
|
|
|
def _manifest_from_library(lib):
|
|
def _manifest_from_library(lib):
|