mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-23 08:00:38 +02:00
Catalogs list each kind's versions with notes, cost and benchmark numbers: hands/models.json (Latest = v13b-p1, stock) and gaze/tracker/models.json (classic, SteamVR, eyes-v0 as Testing). Installs read their branch's catalog from GitHub, so a new version reaches them with a push; offline, the copy in the install is used. models/catalog.py (+ ft-models CLI) validates catalogs strictly, downloads versions from the kind's fixed Hugging Face repo pinned to a commit, and keeps only files matching their sha256. HANDS_MODEL and EYES_MODEL pick latest (follows new versions), a version by name, or a builtin. ft-hands and ft-eyes read them at start; Input Settings restarts hand tracking and ft-gazed restarts ft-eyes when the setting changes. The services fetch what the setting needs at start (HANDS_MODELS_UPDATE / EYES_MODELS_UPDATE = 0 turn that off). Input Settings: "Hand model" on the Hands page, "Eye tracker" on the Gaze page (replaces the Own/SteamVR switch), tagged Recommended / Testing / Older / Built in, with a benchmark table against the model in use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
4.5 KiB
Python
Executable File
92 lines
4.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""ft-models: the hand and eye model catalogs (models/catalog.py) from a terminal.
|
|
|
|
ft-models list hands|eyes [--json] the versions, their notes, and what's in use
|
|
ft-models refresh hands|eyes fetch this install's branch's catalog from GitHub
|
|
ft-models fetch hands|eyes NAME download a version (every file checked against the catalog)
|
|
ft-models verify hands|eyes NAME check a downloaded version's files again
|
|
ft-models remove hands|eyes NAME delete a downloaded version
|
|
ft-models resolve hands|eyes [VALUE] what the setting (or VALUE) runs now, as JSON
|
|
ft-models auto hands|eyes what the services run at start: refresh at most every
|
|
15 minutes, fetch what the setting needs if it's missing
|
|
ft-models hashes hands|eyes STAGING VERSION
|
|
for a new catalog entry: the "files" of STAGING/VERSION and
|
|
STAGING's LICENSE and README.md, as uploaded (models/README.md)
|
|
|
|
Pick a model in Frametop Input Settings (Hands and Gaze pages), or set HANDS_MODEL / EYES_MODEL in
|
|
~/.config/frametop.conf: latest (follows new versions), a version's name, or a builtin (stock for
|
|
hands; classic for eyes, and GAZE_TRACKER=steam for SteamVR's).
|
|
"""
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
import catalog # noqa: E402
|
|
|
|
|
|
def main(argv):
|
|
if len(argv) < 2 or argv[0] not in ("list", "refresh", "fetch", "verify", "remove", "resolve", "auto", "hashes") \
|
|
or argv[1] not in catalog.SPECS:
|
|
print(__doc__.strip(), file=sys.stderr)
|
|
return 2
|
|
cmd, kind, rest = argv[0], argv[1], argv[2:]
|
|
try:
|
|
if cmd == "list":
|
|
v = catalog.view(kind)
|
|
if "--json" in rest:
|
|
print(json.dumps(v, indent=1))
|
|
return 0
|
|
print(f"{kind} models ({v['source']}); setting {v['setting']!r} -> {v['resolved']['why']}")
|
|
for m in v["models"]:
|
|
mark = "*" if m["name"] == v["resolved"]["name"] else " "
|
|
latest = " (latest)" if m["name"] == v["latest"] else ""
|
|
state = "builtin" if m["builtin"] else "downloaded" if m["installed"] else \
|
|
"needs a newer Frametop" if not m["usable"] else "not downloaded"
|
|
print(f"{mark} {m['name']:<16} {m['status']:<12} {state:<15} {m['title']}{latest}")
|
|
if m["summary"]:
|
|
print(f" {'':<16} {m['summary']}")
|
|
return 0
|
|
if cmd == "refresh":
|
|
ok, msg = catalog.refresh(kind)
|
|
print(f"{kind} catalog: {msg}")
|
|
return 0 if ok else 1
|
|
if cmd == "resolve":
|
|
print(json.dumps(catalog.resolve(kind, rest[0] if rest else None)))
|
|
return 0
|
|
if cmd == "auto":
|
|
print(catalog.auto(kind))
|
|
return 0
|
|
if cmd == "hashes" and len(rest) == 2:
|
|
staging, version = Path(rest[0]), rest[1]
|
|
spec = catalog.SPECS[kind]
|
|
files = {p.name: catalog._sha(p) for p in sorted((staging / version).iterdir()) if p.is_file()}
|
|
files.update({n: catalog._sha(staging / n) for n in sorted(spec["top"]) if (staging / n).is_file()})
|
|
if set(files) - spec["files"] - spec["top"] or spec["required"] - set(files):
|
|
print(f"ft-models: a {kind} version has {sorted(spec['files'])} (needs {sorted(spec['required'])}),"
|
|
f" plus {sorted(spec['top'])} at the top; found {sorted(files)}", file=sys.stderr)
|
|
return 1
|
|
print(json.dumps({"files": files}, indent=1))
|
|
return 0
|
|
if not rest:
|
|
print(f"ft-models {cmd} {kind} NAME", file=sys.stderr)
|
|
return 2
|
|
name = rest[0]
|
|
if cmd == "fetch":
|
|
d = catalog.fetch(kind, name, progress=lambda s: print(s, flush=True))
|
|
print(f"{kind} model {name} in {d}")
|
|
elif cmd == "verify":
|
|
bad = catalog.verify(kind, name)
|
|
print(f"{kind} model {name}: " + ("all files match" if not bad else "doesn't match: " + ", ".join(bad)))
|
|
return 1 if bad else 0
|
|
elif cmd == "remove":
|
|
print(f"{kind} model {name}: " + ("removed" if catalog.remove(kind, name) else "wasn't downloaded"))
|
|
except (catalog.CatalogError, OSError, ValueError) as e:
|
|
print(f"ft-models: {e}", file=sys.stderr)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|