nspasojevic commited on
Commit
71ed715
·
1 Parent(s): 3f92f12

Library media from the artifact bucket (<ARTIFACT_S3_URI>/library-media/<corpus>/media.tar), not an HF dataset — indexing PR #24 (6b52aba), via this image's aws-CLI artifact_store (GPU variant)

Browse files
docker/hf-space/nginx.conf.template CHANGED
@@ -262,7 +262,7 @@ server {
262
  }
263
  # Library media — the Explorer's joints viewer (model.glb + articulation.json), moving-parts and drop-test
264
  # clips, per asset of this corpus: static files pipeline/library_media.py fetches at container start from
265
- # SimReady Central's HF dataset (supervisord program library-media). Not there (not fetched yet, or none for
266
  # this corpus) → a JSON 404 and the Explorer shows no media; never the SPA's index.html. Range requests
267
  # (Safari's <video>) are nginx's own. add_header drops his server-level headers: his CORS lines re-declared.
268
  location /library_media/ {
 
262
  }
263
  # Library media — the Explorer's joints viewer (model.glb + articulation.json), moving-parts and drop-test
264
  # clips, per asset of this corpus: static files pipeline/library_media.py fetches at container start from
265
+ # the artifact bucket (supervisord program library-media). Not there (not fetched yet, or none for
266
  # this corpus) → a JSON 404 and the Explorer shows no media; never the SPA's index.html. Range requests
267
  # (Safari's <video>) are nginx's own. add_header drops his server-level headers: his CORS lines re-declared.
268
  location /library_media/ {
docker/hf-space/supervisord-gpu.conf CHANGED
@@ -122,7 +122,7 @@ stderr_logfile_maxbytes=0
122
 
123
  [program:library-media]
124
  ; SimReady Central's library media for this corpus (the Explorer's joints viewer, moving-parts and drop-test
125
- ; clips), fetched once per container start from its HF dataset into .build/library_media/ — see
126
  ; pipeline/library_media.py and nginx.conf.template's /library_media/. Optional: it always exits 0 and is never
127
  ; restarted; until it has finished (or when there is none) /library_media/ answers 404 and the Explorer shows
128
  ; no media.
 
122
 
123
  [program:library-media]
124
  ; SimReady Central's library media for this corpus (the Explorer's joints viewer, moving-parts and drop-test
125
+ ; clips), fetched once per container start from the artifact bucket into .build/library_media/ — see
126
  ; pipeline/library_media.py and nginx.conf.template's /library_media/. Optional: it always exits 0 and is never
127
  ; restarted; until it has finished (or when there is none) /library_media/ answers 404 and the Explorer shows
128
  ; no media.
docker/hf-space/supervisord.conf CHANGED
@@ -120,7 +120,7 @@ stderr_logfile_maxbytes=0
120
 
121
  [program:library-media]
122
  ; SimReady Central's library media for this corpus (the Explorer's joints viewer, moving-parts and drop-test
123
- ; clips), fetched once per container start from its HF dataset into .build/library_media/ — see
124
  ; pipeline/library_media.py and nginx.conf.template's /library_media/. Optional: it always exits 0 and is never
125
  ; restarted; until it has finished (or when there is none) /library_media/ answers 404 and the Explorer shows
126
  ; no media.
 
120
 
121
  [program:library-media]
122
  ; SimReady Central's library media for this corpus (the Explorer's joints viewer, moving-parts and drop-test
123
+ ; clips), fetched once per container start from the artifact bucket into .build/library_media/ — see
124
  ; pipeline/library_media.py and nginx.conf.template's /library_media/. Optional: it always exits 0 and is never
125
  ; restarted; until it has finished (or when there is none) /library_media/ answers 404 and the Explorer shows
126
  ; no media.
pipeline/library_media.py CHANGED
@@ -1,20 +1,21 @@
1
  """Library media for the Space's Explorer: the joints viewer's models, moving-parts and drop-test clips.
2
 
3
  SimReady Central builds these per asset — model.glb + articulation.json for each prop with moving joints
4
- (its scripts/library_media/build.py), the moving-parts clips its lab render pool renders, and the drop tests —
5
- and publishes them as a DATA artifact: a Hugging Face dataset (LIBRARY_MEDIA_REPO, default
6
- nvidia/simready-central-library-media) holding, per corpus, ``<CORPUS_NAME>/media.tar``: an uncompressed tar of
7
- ``manifest.json`` + ``<id>/<file>`` (its members are already compressed). Run once at container start by supervisord
8
- (program ``library-media``), this downloads that ONE file — thousands of small files fetched one by one hit the
9
- Hub's per-IP rate limit on a Space (429s, then a failed fetch, seen live) — unpacks it and installs the tree as
10
- ``.build/library_media/<INDEX_KEY>/``; nginx serves it at /library_media/ (docker/hf-space/
11
- nginx.conf.template), where the Explorer asks for /library_media/usdsearch-dev/manifest.json — usdsearch-dev being
12
- its index key in /config.js.
13
-
14
- Nothing here is needed to search. Any failure — no such repo or archive, a private repo with no token, a network
15
- error, an unsafe or malformed archive — leaves the previous tree (none at boot): nginx answers 404 and the Explorer
16
- shows no media. LIBRARY_MEDIA_REPO=off disables the fetch; LIBRARY_MEDIA_REVISION pins a dataset commit (default
17
- main). The token is HF_STORAGE_API_TOKEN, else HF_TOKEN, else none (the dataset is public).
 
18
  """
19
 
20
  from __future__ import annotations
@@ -22,20 +23,25 @@ from __future__ import annotations
22
  import json
23
  import os
24
  import shutil
 
25
  import sys
26
  import tarfile
27
  import time
28
  from pathlib import Path
29
 
30
- from .config import BUILD_DIR
31
 
32
- LIBRARY_MEDIA_REPO_DEFAULT = "nvidia/simready-central-library-media"
33
  ARCHIVE = "media.tar"
 
34
  # The Explorer's index key for this Space (/config.js in nginx.conf.template): the manifest's URL names it.
35
  INDEX_KEY = "usdsearch-dev"
36
- # NOT under .build/restore/: the index restore (restore.download) empties that whole directory at every start.
37
  MEDIA_ROOT = BUILD_DIR / "library_media"
38
- DISABLED = {"", "off", "none", "false", "0"}
 
 
 
 
 
39
 
40
 
41
  def valid_manifest(path: Path) -> int | None:
@@ -73,48 +79,65 @@ def install(src: Path, dest: Path) -> None:
73
  shutil.rmtree(old, ignore_errors=True)
74
 
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  def fetch(env: dict[str, str], root: Path = MEDIA_ROOT, download=None) -> int:
77
  """Fetch this corpus's media into ``root/INDEX_KEY``; returns the number of assets installed (0: none)."""
78
- repo = env.get("LIBRARY_MEDIA_REPO", LIBRARY_MEDIA_REPO_DEFAULT).strip()
79
  corpus = env.get("CORPUS_NAME", "").strip()
80
- if repo.lower() in DISABLED:
81
- print("==> library media: off (LIBRARY_MEDIA_REPO)")
82
  return 0
83
  if not corpus or "/" in corpus or corpus.startswith("."):
84
  print(f"==> library media: no usable CORPUS_NAME ({corpus!r}), skipped")
85
  return 0
86
  if download is None:
87
- from huggingface_hub import hf_hub_download as download
88
- token = env.get("HF_STORAGE_API_TOKEN", "").strip() or env.get("HF_TOKEN", "").strip() or None
89
- revision = env.get("LIBRARY_MEDIA_REVISION", "").strip() or None
90
  root.mkdir(parents=True, exist_ok=True)
91
  tmp = root / f".{INDEX_KEY}.download"
92
  shutil.rmtree(tmp, ignore_errors=True)
93
  t0 = time.time()
94
  try:
95
- archive = Path(download(repo_id=repo, repo_type="dataset", revision=revision, filename=f"{corpus}/{ARCHIVE}",
96
- local_dir=str(tmp / "dl"), token=token))
 
 
97
  unpack(archive, tmp / "tree")
98
  count = valid_manifest(tmp / "tree" / "manifest.json")
99
  if count is None:
100
- print(f"==> library media: {repo}/{corpus}/{ARCHIVE} has no usable manifest.json — none served")
101
  return 0
102
  install(tmp / "tree", root / INDEX_KEY)
103
  finally:
104
  shutil.rmtree(tmp, ignore_errors=True)
105
  size = sum(p.stat().st_size for p in (root / INDEX_KEY).rglob("*") if p.is_file())
106
- print(f"==> library media: {count} assets from {repo}/{corpus}/{ARCHIVE} ({size / 1e6:.0f} MB) "
107
- f"in {time.time() - t0:.0f} s")
108
  return count
109
 
110
 
111
  def main() -> int:
112
- # supervisord runs this as uid 1000 with root's HOME: huggingface_hub's default home (~/.cache/huggingface, its
113
- # token file and caches) is unreadable there, so it gets one under .build/ unless the image sets its own.
114
- os.environ.setdefault("HF_HOME", str(BUILD_DIR / "hf-home"))
115
  try:
116
  fetch(dict(os.environ))
117
- except Exception as exc: # noqa: BLE001 — media are optional: never a failed program, never a retry loop
 
118
  print(f"==> library media: not fetched ({type(exc).__name__}: {str(exc)[:300]}) — none served", file=sys.stderr)
119
  return 0
120
 
 
1
  """Library media for the Space's Explorer: the joints viewer's models, moving-parts and drop-test clips.
2
 
3
  SimReady Central builds these per asset — model.glb + articulation.json for each prop with moving joints
4
+ (its scripts/library_media/build.py), the moving-parts clips its lab render pool renders, and the drop tests — and
5
+ they are kept in the artifact bucket next to the index runs, one archive per corpus:
6
+ ``<ARTIFACT_S3_URI>/library-media/<CORPUS_NAME>/media.tar``, an uncompressed tar of ``manifest.json`` +
7
+ ``<id>/<file>`` (its members are already compressed). Outside ``<corpus>/``, so nothing that lists a corpus's runs
8
+ sees it; corpus-level, not per run, because the media are keyed by the assets' URLs, not by an index build.
9
+
10
+ Run once at container start by supervisord (program ``library-media``), this downloads that ONE object with the
11
+ same aws CLI and credentials as the index restore (artifact_store), unpacks it and installs the tree as
12
+ ``.build/library_media/<INDEX_KEY>/``; nginx serves it at /library_media/ (docker/hf-space/nginx.conf.template), where
13
+ the Explorer asks for /library_media/usdsearch-dev/manifest.json — usdsearch-dev being its index key in /config.js.
14
+ NOT under ``.build/restore/``: restore.download empties that whole directory at every start.
15
+
16
+ Nothing here is needed to search. Any failure — no archive for this corpus, no bucket credentials, a network error,
17
+ an unsafe or malformed archive — leaves the previous tree (none at boot): nginx answers 404 and the Explorer shows no
18
+ media. LIBRARY_MEDIA=off disables the fetch.
19
  """
20
 
21
  from __future__ import annotations
 
23
  import json
24
  import os
25
  import shutil
26
+ import subprocess
27
  import sys
28
  import tarfile
29
  import time
30
  from pathlib import Path
31
 
32
+ from .config import BUILD_DIR, Config
33
 
 
34
  ARCHIVE = "media.tar"
35
+ PREFIX = "library-media"
36
  # The Explorer's index key for this Space (/config.js in nginx.conf.template): the manifest's URL names it.
37
  INDEX_KEY = "usdsearch-dev"
 
38
  MEDIA_ROOT = BUILD_DIR / "library_media"
39
+ DISABLED = {"off", "none", "false", "0"}
40
+
41
+
42
+ def archive_uri(cfg: Config, corpus: str) -> str:
43
+ """Where this corpus's media archive lives in the artifact bucket."""
44
+ return f"{cfg.require('ARTIFACT_S3_URI').rstrip('/')}/{PREFIX}/{corpus}/{ARCHIVE}"
45
 
46
 
47
  def valid_manifest(path: Path) -> int | None:
 
79
  shutil.rmtree(old, ignore_errors=True)
80
 
81
 
82
+ def cli_download(cfg: Config, uri: str, dest: Path, attempts: int = 3) -> bool:
83
+ """``aws s3 cp`` ``uri`` to ``dest``; False when there is no such object (this image's artifact_store is the
84
+ aws-CLI one, before indexing PR #25's ``download_file``)."""
85
+ from .artifact_store import artifact_env, aws_cmd
86
+ dest.parent.mkdir(parents=True, exist_ok=True)
87
+ for attempt in range(1, attempts + 1):
88
+ r = subprocess.run(aws_cmd(cfg) + ["cp", uri, str(dest), "--only-show-errors"],
89
+ capture_output=True, text=True, env=artifact_env(), timeout=1800)
90
+ if r.returncode == 0:
91
+ return True
92
+ err = (r.stderr or r.stdout).strip()
93
+ if "(404)" in err or "Not Found" in err or "does not exist" in err:
94
+ return False
95
+ print(f"==> library media: aws s3 cp failed (attempt {attempt}/{attempts}, exit {r.returncode}): {err[:300]}")
96
+ if attempt < attempts:
97
+ time.sleep(5 * attempt)
98
+ raise RuntimeError(f"aws s3 cp {uri} failed {attempts} times")
99
+
100
+
101
  def fetch(env: dict[str, str], root: Path = MEDIA_ROOT, download=None) -> int:
102
  """Fetch this corpus's media into ``root/INDEX_KEY``; returns the number of assets installed (0: none)."""
 
103
  corpus = env.get("CORPUS_NAME", "").strip()
104
+ if env.get("LIBRARY_MEDIA", "").strip().lower() in DISABLED:
105
+ print("==> library media: off (LIBRARY_MEDIA)")
106
  return 0
107
  if not corpus or "/" in corpus or corpus.startswith("."):
108
  print(f"==> library media: no usable CORPUS_NAME ({corpus!r}), skipped")
109
  return 0
110
  if download is None:
111
+ download = cli_download
112
+ cfg = Config(values=dict(env))
113
+ uri = archive_uri(cfg, corpus)
114
  root.mkdir(parents=True, exist_ok=True)
115
  tmp = root / f".{INDEX_KEY}.download"
116
  shutil.rmtree(tmp, ignore_errors=True)
117
  t0 = time.time()
118
  try:
119
+ archive = tmp / ARCHIVE
120
+ if not download(cfg, uri, archive):
121
+ print(f"==> library media: no {uri} — none served")
122
+ return 0
123
  unpack(archive, tmp / "tree")
124
  count = valid_manifest(tmp / "tree" / "manifest.json")
125
  if count is None:
126
+ print(f"==> library media: {uri} has no usable manifest.json — none served")
127
  return 0
128
  install(tmp / "tree", root / INDEX_KEY)
129
  finally:
130
  shutil.rmtree(tmp, ignore_errors=True)
131
  size = sum(p.stat().st_size for p in (root / INDEX_KEY).rglob("*") if p.is_file())
132
+ print(f"==> library media: {count} assets from {uri} ({size / 1e6:.0f} MB) in {time.time() - t0:.0f} s")
 
133
  return count
134
 
135
 
136
  def main() -> int:
 
 
 
137
  try:
138
  fetch(dict(os.environ))
139
+ except (Exception, SystemExit) as exc: # noqa: BLE001 — media are optional: never a failed program, never a retry loop
140
+ # (SystemExit too: artifact_store raises it when the bucket credentials are missing.)
141
  print(f"==> library media: not fetched ({type(exc).__name__}: {str(exc)[:300]}) — none served", file=sys.stderr)
142
  return 0
143