immersive-studio 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- immersive_studio-0.1.0/PKG-INFO +413 -0
- immersive_studio-0.1.0/README.md +371 -0
- immersive_studio-0.1.0/pyproject.toml +78 -0
- immersive_studio-0.1.0/setup.cfg +4 -0
- immersive_studio-0.1.0/src/immersive_studio/__init__.py +54 -0
- immersive_studio-0.1.0/src/immersive_studio.egg-info/PKG-INFO +413 -0
- immersive_studio-0.1.0/src/immersive_studio.egg-info/SOURCES.txt +68 -0
- immersive_studio-0.1.0/src/immersive_studio.egg-info/dependency_links.txt +1 -0
- immersive_studio-0.1.0/src/immersive_studio.egg-info/entry_points.txt +2 -0
- immersive_studio-0.1.0/src/immersive_studio.egg-info/requires.txt +24 -0
- immersive_studio-0.1.0/src/immersive_studio.egg-info/top_level.txt +2 -0
- immersive_studio-0.1.0/src/studio_worker/__init__.py +3 -0
- immersive_studio-0.1.0/src/studio_worker/__main__.py +4 -0
- immersive_studio-0.1.0/src/studio_worker/api.py +461 -0
- immersive_studio-0.1.0/src/studio_worker/attribution.py +93 -0
- immersive_studio-0.1.0/src/studio_worker/billing_config.py +111 -0
- immersive_studio-0.1.0/src/studio_worker/billing_routes.py +137 -0
- immersive_studio-0.1.0/src/studio_worker/blender/export_mesh.py +355 -0
- immersive_studio-0.1.0/src/studio_worker/cli.py +338 -0
- immersive_studio-0.1.0/src/studio_worker/comfy_client.py +200 -0
- immersive_studio-0.1.0/src/studio_worker/comfy_workflows/sd15_albedo_v1.api.json +59 -0
- immersive_studio-0.1.0/src/studio_worker/comfy_workflows/sdxl_albedo_v1.api.json +59 -0
- immersive_studio-0.1.0/src/studio_worker/data/studio-asset-spec-v0.1.schema.json +106 -0
- immersive_studio-0.1.0/src/studio_worker/job_artifacts.py +112 -0
- immersive_studio-0.1.0/src/studio_worker/job_runner.py +192 -0
- immersive_studio-0.1.0/src/studio_worker/jobs_store.py +165 -0
- immersive_studio-0.1.0/src/studio_worker/json_extract.py +39 -0
- immersive_studio-0.1.0/src/studio_worker/manifest.py +40 -0
- immersive_studio-0.1.0/src/studio_worker/mesh_export.py +140 -0
- immersive_studio-0.1.0/src/studio_worker/mock_spec.py +75 -0
- immersive_studio-0.1.0/src/studio_worker/moderation.py +43 -0
- immersive_studio-0.1.0/src/studio_worker/ollama_client.py +44 -0
- immersive_studio-0.1.0/src/studio_worker/pack_writer.py +114 -0
- immersive_studio-0.1.0/src/studio_worker/paths.py +86 -0
- immersive_studio-0.1.0/src/studio_worker/pbr_keys.py +62 -0
- immersive_studio-0.1.0/src/studio_worker/prompts.py +62 -0
- immersive_studio-0.1.0/src/studio_worker/queue_executor.py +37 -0
- immersive_studio-0.1.0/src/studio_worker/queue_postgres.py +447 -0
- immersive_studio-0.1.0/src/studio_worker/queue_redis.py +418 -0
- immersive_studio-0.1.0/src/studio_worker/queue_redis_streams.py +481 -0
- immersive_studio-0.1.0/src/studio_worker/queue_sqlite.py +426 -0
- immersive_studio-0.1.0/src/studio_worker/queue_sqs_postgres.py +218 -0
- immersive_studio-0.1.0/src/studio_worker/quotas.py +82 -0
- immersive_studio-0.1.0/src/studio_worker/scale_config.py +166 -0
- immersive_studio-0.1.0/src/studio_worker/spec_generate.py +61 -0
- immersive_studio-0.1.0/src/studio_worker/sqlite_queue.py +53 -0
- immersive_studio-0.1.0/src/studio_worker/stripe_billing.py +314 -0
- immersive_studio-0.1.0/src/studio_worker/tenant_context.py +84 -0
- immersive_studio-0.1.0/src/studio_worker/tenants_db.py +70 -0
- immersive_studio-0.1.0/src/studio_worker/tenants_postgres.py +396 -0
- immersive_studio-0.1.0/src/studio_worker/tenants_sqlite.py +414 -0
- immersive_studio-0.1.0/src/studio_worker/texture_pipeline.py +174 -0
- immersive_studio-0.1.0/src/studio_worker/tiers.py +60 -0
- immersive_studio-0.1.0/src/studio_worker/validate.py +121 -0
- immersive_studio-0.1.0/src/studio_worker/workflow_template.py +53 -0
- immersive_studio-0.1.0/src/studio_worker/zip_pack.py +23 -0
- immersive_studio-0.1.0/tests/test_comfy_client_mock.py +64 -0
- immersive_studio-0.1.0/tests/test_gpu_comfy_placeholder.py +47 -0
- immersive_studio-0.1.0/tests/test_immersive_studio_sdk.py +28 -0
- immersive_studio-0.1.0/tests/test_mesh_export.py +80 -0
- immersive_studio-0.1.0/tests/test_moderation.py +14 -0
- immersive_studio-0.1.0/tests/test_pbr_keys.py +53 -0
- immersive_studio-0.1.0/tests/test_queue_api_idempotency.py +59 -0
- immersive_studio-0.1.0/tests/test_queue_redis.py +78 -0
- immersive_studio-0.1.0/tests/test_queue_redis_streams.py +85 -0
- immersive_studio-0.1.0/tests/test_sqlite_queue.py +102 -0
- immersive_studio-0.1.0/tests/test_stripe_billing.py +202 -0
- immersive_studio-0.1.0/tests/test_tenant_auth.py +101 -0
- immersive_studio-0.1.0/tests/test_validate.py +22 -0
- immersive_studio-0.1.0/tests/test_zip_pack.py +18 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: immersive-studio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Immersive Studio CLI and SDK: spec generation, ComfyUI textures, Unity packs, and HTTP worker API.
|
|
5
|
+
Author: Immersive Labs
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/immersive-labs/immersive.labs
|
|
8
|
+
Project-URL: Documentation, https://github.com/immersive-labs/immersive.labs/tree/main/apps/studio-worker
|
|
9
|
+
Project-URL: Repository, https://github.com/immersive-labs/immersive.labs
|
|
10
|
+
Project-URL: Issues, https://github.com/immersive-labs/immersive.labs/issues
|
|
11
|
+
Keywords: cli,game-dev,unity,comfyui,3d,procedural
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Games/Entertainment
|
|
20
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Requires-Dist: fastapi>=0.115
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: jsonschema>=4.21
|
|
26
|
+
Requires-Dist: stripe>=10.0
|
|
27
|
+
Requires-Dist: uvicorn[standard]>=0.32
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
|
+
Requires-Dist: respx>=0.21.0; extra == "dev"
|
|
31
|
+
Requires-Dist: fakeredis>=2.23; extra == "dev"
|
|
32
|
+
Provides-Extra: postgres
|
|
33
|
+
Requires-Dist: psycopg[binary]>=3.2; extra == "postgres"
|
|
34
|
+
Provides-Extra: redis
|
|
35
|
+
Requires-Dist: redis>=5.0; extra == "redis"
|
|
36
|
+
Provides-Extra: s3
|
|
37
|
+
Requires-Dist: boto3>=1.34; extra == "s3"
|
|
38
|
+
Provides-Extra: scale
|
|
39
|
+
Requires-Dist: psycopg[binary]>=3.2; extra == "scale"
|
|
40
|
+
Requires-Dist: boto3>=1.34; extra == "scale"
|
|
41
|
+
Requires-Dist: redis>=5.0; extra == "scale"
|
|
42
|
+
|
|
43
|
+
# Immersive Studio (CLI + SDK)
|
|
44
|
+
|
|
45
|
+
Publishable **PyPI** package **`immersive-studio`**: the **`immersive-studio`** terminal command, importable Python SDK **`immersive_studio`**, and the same **FastAPI** worker used by the **Video Game Generation Studio** (`/studio` in `@immersive/web`). Features include Ollama/mock spec generation, **ComfyUI** texture passes, optional **Blender** placeholder `.glb`, **Unity** pack layout (`manifest.json`, `spec.json`, `pack.zip`), and the **HTTP API**.
|
|
46
|
+
|
|
47
|
+
## Install (PyPI — like any other CLI)
|
|
48
|
+
|
|
49
|
+
After the package is published on PyPI as **`immersive-studio`** (maintainers: see **Publishing** below), end users can install globally with [**pipx**](https://pipx.pypa.io/) so the executable lands on `PATH` without touching system Python:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pipx install immersive-studio
|
|
53
|
+
immersive-studio doctor
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or into the active virtual environment:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install immersive-studio
|
|
60
|
+
python -m studio_worker.cli doctor
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Optional extras:** `pip install immersive-studio[postgres]`, `[redis]`, `[s3]`, or `[scale]` for production-style backends (see Environment table below).
|
|
64
|
+
|
|
65
|
+
> **Note:** The distribution was previously named `immersive-studio-worker`. Use **`immersive-studio`** going forward.
|
|
66
|
+
|
|
67
|
+
## Python SDK
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from immersive_studio import __version__, run_studio_job
|
|
71
|
+
|
|
72
|
+
result = run_studio_job(
|
|
73
|
+
user_prompt="wooden barrel",
|
|
74
|
+
category="prop",
|
|
75
|
+
style_preset="toon_bold",
|
|
76
|
+
use_mock=True,
|
|
77
|
+
generate_textures=False,
|
|
78
|
+
unity_urp_hint="6000.0.x LTS (pin when smoke-tested)",
|
|
79
|
+
export_mesh=False,
|
|
80
|
+
)
|
|
81
|
+
print(result["job_id"], result["zip_path"])
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Public symbols: `__version__`, `run_studio_job`, `validate_asset_spec_file`, `generate_asset_spec_with_metadata`, `comfy_base_url`, `comfy_reachability`. Advanced integrations can import from the `studio_worker` package directly.
|
|
85
|
+
|
|
86
|
+
## Publishing to PyPI
|
|
87
|
+
|
|
88
|
+
I cannot create or “register” a PyPI project on your behalf (that requires you to sign in at [pypi.org](https://pypi.org)). Use an API token as below to upload from CI or your laptop.
|
|
89
|
+
|
|
90
|
+
### GitHub Actions publish (this repo)
|
|
91
|
+
|
|
92
|
+
The workflow **`.github/workflows/publish-immersive-studio.yml`** uploads with **`twine`** only. It **does not** use OIDC or `pypa/gh-action-pypi-publish`, so you will not see **`invalid-publisher`** from that path.
|
|
93
|
+
|
|
94
|
+
1. Create a PyPI **API token** with **upload** scope for **`immersive-studio`** ([PyPI → API tokens](https://pypi.org/manage/account/token/)).
|
|
95
|
+
2. In GitHub: **Settings → Secrets and variables → Actions** → **Repository secrets** → **New repository secret**.
|
|
96
|
+
- **Name:** `PYPI_API_TOKEN` (exact spelling, all caps).
|
|
97
|
+
- **Value:** the token string (often starts with `pypi-`).
|
|
98
|
+
Do **not** put it under *Dependabot* or *Codespaces* only; it must be a **repository** secret for this repo.
|
|
99
|
+
3. Run **Actions → “PyPI publish (immersive-studio package)” → Run workflow** on **`main`**, or publish a **GitHub Release** to trigger it. Use a **new** run after changing secrets (re-running a very old failed job can still use old workflow YAML).
|
|
100
|
+
|
|
101
|
+
If the job fails immediately with **“PYPI_API_TOKEN is missing or empty”**, GitHub did not inject the secret (wrong name, empty value, or workflow running from a fork without secrets).
|
|
102
|
+
|
|
103
|
+
### Optional: PyPI trusted publishing (OIDC)
|
|
104
|
+
|
|
105
|
+
OIDC is **not** used by the publish workflow above. To use trusted publishing instead, upload from your machine with `twine` or configure a **separate** workflow; PyPI’s form needs your **owner/repo**, workflow filename **`publish-immersive-studio.yml`**, and an **empty** environment name unless you add a matching GitHub Environment.
|
|
106
|
+
|
|
107
|
+
### Manual upload (API token)
|
|
108
|
+
|
|
109
|
+
From `apps/studio-worker` with [build](https://pypi.org/project/build/) and [twine](https://twine.readthedocs.io/):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install build twine
|
|
113
|
+
python -m build
|
|
114
|
+
twine upload dist/*
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Use a [PyPI API token](https://pypi.org/manage/account/token/) with upload scope; the first upload can create the project if the name is free.
|
|
118
|
+
|
|
119
|
+
If the project name **`immersive-studio`** is already taken on PyPI, change `name` in `pyproject.toml` to an available name (for example `immersive-labs-studio`) and update install docs accordingly.
|
|
120
|
+
|
|
121
|
+
When you change **`packages/studio-types/schema/studio-asset-spec-v0.1.schema.json`**, copy the file to **`src/studio_worker/data/studio-asset-spec-v0.1.schema.json`** before releasing so the wheel matches the canonical schema.
|
|
122
|
+
|
|
123
|
+
## Setup (monorepo / contributors)
|
|
124
|
+
|
|
125
|
+
From repository root:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cd apps/studio-worker
|
|
129
|
+
python -m venv .venv
|
|
130
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
131
|
+
pip install -e .
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Invoking the CLI:** After `pip install -e .`, the `immersive-studio` command lives next to your Python interpreter (for example `.venv\Scripts\immersive-studio.exe`, or `%APPDATA%\Python\Python3xx\Scripts\immersive-studio.exe` when pip used a **user** install). If your shell says `command not found`, either add that `Scripts` folder to your **PATH**, or call the module directly (works from any directory once the package is installed):
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python -m studio_worker.cli doctor
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
From this app directory you can also use the repo wrappers (no PATH change):
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
./scripts/immersive-studio.sh doctor # Git Bash / MSYS
|
|
144
|
+
scripts\immersive-studio.cmd doctor # cmd.exe / PowerShell
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Ensure [Ollama](https://ollama.com/) is running locally if you use LLM generation (default `http://127.0.0.1:11434`). Pull a JSON-capable model, for example:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
ollama pull llama3.2
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
For **texture generation**, run [ComfyUI](https://github.com/comfyanonymous/ComfyUI) (default `http://127.0.0.1:8188`) and install a checkpoint that matches `STUDIO_COMFY_CHECKPOINT`. See [`comfy/README.md`](./comfy/README.md).
|
|
154
|
+
|
|
155
|
+
After installing ComfyUI and (for mesh export) [Blender](https://www.blender.org/), verify the worker can see both:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
python -m studio_worker.cli doctor
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Exit code `0` means ComfyUI answered at `STUDIO_COMFY_URL` (default `http://127.0.0.1:8188`) and Blender was resolved (PATH, `STUDIO_BLENDER_BIN`, or a standard Windows/macOS install path). Use `python -m studio_worker.cli doctor --comfy-url http://host:8188` to probe a different URL once.
|
|
162
|
+
|
|
163
|
+
To launch ComfyUI from a git checkout once dependencies are installed, set **`COMFYUI_ROOT`** and run [`scripts/start-comfyui-dev.sh`](./scripts/start-comfyui-dev.sh) or [`scripts/start-comfyui-dev.ps1`](./scripts/start-comfyui-dev.ps1) (listens on `127.0.0.1:8188`).
|
|
164
|
+
|
|
165
|
+
## Docker (VM / production-shaped)
|
|
166
|
+
|
|
167
|
+
The image copies **`packages/studio-types/schema`** and **`apps/studio-worker`** into a layout that matches `STUDIO_REPO_ROOT` (defaults to **`/repo`** in the image).
|
|
168
|
+
|
|
169
|
+
**Build** (from **monorepo root**):
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
docker build -f apps/studio-worker/Dockerfile -t immersive-studio-worker:local .
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Run** (set CORS to every **browser origin** that loads `/studio` — comma-separated, no trailing slashes):
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
docker run -d --name studio-worker --restart unless-stopped \
|
|
179
|
+
-p 8787:8787 \
|
|
180
|
+
-e STUDIO_CORS_ORIGINS='https://YOUR-APP.vercel.app,https://www.yourdomain.com,https://yourdomain.com' \
|
|
181
|
+
-v studio-output:/repo/apps/studio-worker/output \
|
|
182
|
+
immersive-studio-worker:local
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
If you only use the default Vercel hostname, a single origin is enough: `https://YOUR-APP.vercel.app`. Trailing slashes in the env var are ignored.
|
|
186
|
+
|
|
187
|
+
On a cloud VM behind **Cloudflare Tunnel**, bind Docker to localhost only: **`-p 127.0.0.1:8787:8787`** (see [deploy-gcp-free-vm.md](../../docs/studio/deploy-gcp-free-vm.md)).
|
|
188
|
+
|
|
189
|
+
**Compose** (dev smoke test; overrides `STUDIO_CORS_ORIGINS` as needed):
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
docker compose -f apps/studio-worker/docker-compose.yml up --build
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Free GCP VM + HTTPS + Vercel:** [docs/studio/deploy-gcp-free-vm.md](../../docs/studio/deploy-gcp-free-vm.md) (includes **`scripts/studio-cloudflare-tunnel/`** for `gcloud` + **Cloudflare Tunnel**).
|
|
196
|
+
|
|
197
|
+
## CLI
|
|
198
|
+
|
|
199
|
+
In the examples below, `immersive-studio` is shorthand for **`python -m studio_worker.cli`** (or the `.exe` on your PATH after you add Python’s `Scripts` folder — see **Setup**).
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Generate spec via Ollama (default model from env STUDIO_OLLAMA_MODEL or llama3.2)
|
|
203
|
+
immersive-studio generate-spec --prompt "wooden crate with iron bands" --style-preset toon_bold --category prop
|
|
204
|
+
|
|
205
|
+
# Validated mock spec without Ollama (CI / offline)
|
|
206
|
+
immersive-studio generate-spec --mock --prompt "neon barrel" --style-preset anime_stylized --category prop
|
|
207
|
+
|
|
208
|
+
# Validate a JSON file against schema + studio rules
|
|
209
|
+
immersive-studio validate-spec --file ./spec.json
|
|
210
|
+
|
|
211
|
+
# Write pack folder: manifest.json, spec.json, UnityImportNotes.md, Models/ + Textures/ layout
|
|
212
|
+
immersive-studio pack --spec ./spec.json --output ./out/MyPack
|
|
213
|
+
|
|
214
|
+
# Full persisted job under output/jobs/ (spec + pack + zip + index); optional ComfyUI textures
|
|
215
|
+
immersive-studio run-job --mock --prompt "wooden barrel" --textures
|
|
216
|
+
|
|
217
|
+
# Optional: ComfyUI on a non-default URL for this run only
|
|
218
|
+
immersive-studio run-job --mock --prompt "wooden barrel" --textures --comfy-url http://127.0.0.1:8188
|
|
219
|
+
|
|
220
|
+
# Same, plus headless Blender placeholder `.glb` under Models/ (requires Blender on PATH or STUDIO_BLENDER_BIN)
|
|
221
|
+
immersive-studio run-job --mock --prompt "crate" --export-mesh
|
|
222
|
+
|
|
223
|
+
# Check ComfyUI + Blender before a full job
|
|
224
|
+
immersive-studio doctor
|
|
225
|
+
|
|
226
|
+
# HTTP API for the web UI (CORS allows Vite dev server)
|
|
227
|
+
immersive-studio serve --host 127.0.0.1 --port 8787
|
|
228
|
+
|
|
229
|
+
# Poll SQLite-backed job queue (run multiple terminals or processes for parallel workers)
|
|
230
|
+
immersive-studio queue-worker --worker-id worker-a
|
|
231
|
+
immersive-studio queue-worker --once --worker-id one-shot
|
|
232
|
+
|
|
233
|
+
# Multi-tenant / indie SaaS (after STUDIO_API_AUTH_REQUIRED=1 on the server)
|
|
234
|
+
immersive-studio tenants create --name "Pixel Friends" --tier indie
|
|
235
|
+
immersive-studio tenants issue-key --tenant-id <uuid-from-create> --label "lead-dev-laptop"
|
|
236
|
+
immersive-studio tenants list
|
|
237
|
+
immersive-studio tenants set-tier --tenant-id <uuid> --tier team
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## HTTP API (selected)
|
|
241
|
+
|
|
242
|
+
| Method | Path | Purpose |
|
|
243
|
+
|--------|------|---------|
|
|
244
|
+
| GET | `/api/studio/health` | Worker liveness (`auth_required` flag for clients) |
|
|
245
|
+
| GET | `/api/studio/metrics` | Queue counts by status + `jobs_indexed` (tenant-scoped when auth on) |
|
|
246
|
+
| GET | `/api/studio/comfy-status` | ComfyUI `/system_stats` probe (no API key) |
|
|
247
|
+
| GET | `/api/studio/usage` | Tier + monthly credits used/cap (requires key when auth on) |
|
|
248
|
+
| POST | `/api/studio/generate-spec` | Prompt → validated spec |
|
|
249
|
+
| POST | `/api/studio/pack` | Ad-hoc pack (scoped under `output/packs/…` when auth on) |
|
|
250
|
+
| POST | `/api/studio/jobs/run` | Full job → `output/jobs/job_*/` + `pack.zip` + index |
|
|
251
|
+
| POST | `/api/studio/queue/jobs` | Enqueue async job (SQLite or Postgres); optional `idempotency_key` dedupes per tenant; response `deduplicated` |
|
|
252
|
+
| GET | `/api/studio/queue/jobs` | Recent queue rows (tenant-scoped when auth on) |
|
|
253
|
+
| GET | `/api/studio/queue/jobs/{queue_id}` | Single queue row |
|
|
254
|
+
| GET | `/api/studio/jobs` | List persisted jobs + `jobs_root` path (tenant-scoped when auth on) |
|
|
255
|
+
| GET | `/api/studio/jobs/{job_id}/download` | Download `pack.zip` (local file) or **302** to a remote URL when `STUDIO_JOB_ARTIFACTS` uploads zips |
|
|
256
|
+
| GET | `/api/studio/paths` | Debug paths + active backends (`queue_backend`, `redis_queue_engine` when Redis, `tenants_backend`, …) |
|
|
257
|
+
| GET | `/api/studio/billing/status` | Stripe readiness + linked customer (requires key when auth on) |
|
|
258
|
+
| POST | `/api/studio/billing/checkout-session` | Returns Stripe Checkout URL for `{ "tier": "indie" \| "team" }` |
|
|
259
|
+
| POST | `/api/studio/billing/portal-session` | Returns Stripe Customer Portal URL (manage/cancel) |
|
|
260
|
+
| POST | `/api/studio/billing/webhook` | Stripe webhooks (signing secret); **no API key** |
|
|
261
|
+
|
|
262
|
+
Remote clients send **`Authorization: Bearer <api_key>`** or **`X-API-Key`**. The `/studio` UI stores an optional key in `localStorage` and uses it for fetch + zip download.
|
|
263
|
+
|
|
264
|
+
## Environment
|
|
265
|
+
|
|
266
|
+
| Variable | Default | Purpose |
|
|
267
|
+
|----------|---------|---------|
|
|
268
|
+
| `STUDIO_OLLAMA_URL` | `http://127.0.0.1:11434` | Ollama base URL |
|
|
269
|
+
| `STUDIO_OLLAMA_MODEL` | `llama3.2` | Chat model name |
|
|
270
|
+
| `STUDIO_REPO_ROOT` | — | When set, writable job/queue data uses `apps/studio-worker/output` under this monorepo root (Docker / local dev). |
|
|
271
|
+
| `STUDIO_WORKER_DATA_DIR` | — | Override writable root for jobs, `queue.sqlite`, and `tenants.sqlite` (default: `~/.immersive-studio/worker` when `STUDIO_REPO_ROOT` is unset). |
|
|
272
|
+
| `STUDIO_COMFY_URL` | `http://127.0.0.1:8188` | ComfyUI server |
|
|
273
|
+
| `STUDIO_COMFY_PROFILE` | `sd15` | `sd15` or `sdxl` (selects workflow + latent size) |
|
|
274
|
+
| `STUDIO_COMFY_CHECKPOINT` | profile-specific | Checkpoint **file name** as shown in ComfyUI |
|
|
275
|
+
| `STUDIO_TEXTURE_MAX_IMAGES` | `32` | Cap variant×slot ComfyUI renders per job (albedo/normal/orm) |
|
|
276
|
+
| `STUDIO_JOBS_MAX_COUNT` | `200` | Index cap; oldest job folders deleted when trimming |
|
|
277
|
+
| `STUDIO_JOBS_MAX_TOTAL_BYTES` | `5368709120` | Target max bytes under `output/jobs/` before pruning |
|
|
278
|
+
| `STUDIO_QUOTAS_DISABLED` | — | Set to `1` to skip quota enforcement |
|
|
279
|
+
| `STUDIO_PROMPT_BLOCKLIST` | — | Comma-separated substrings to block in prompts |
|
|
280
|
+
| `STUDIO_MODERATION_DISABLED` | — | Set to `1` to skip moderation |
|
|
281
|
+
| `STUDIO_API_AUTH_REQUIRED` | off | Set to `1` to require API keys for studio routes (indie SaaS mode) |
|
|
282
|
+
| `STUDIO_CORS_ORIGINS` | Vite dev origins | Comma-separated browser origins, or `*` for any (use carefully in production) |
|
|
283
|
+
| `STRIPE_SECRET_KEY` | — | Secret API key (Dashboard → Developers) — required for Checkout / Portal |
|
|
284
|
+
| `STRIPE_WEBHOOK_SECRET` | — | Webhook signing secret for `POST /api/studio/billing/webhook` |
|
|
285
|
+
| `STRIPE_API_VERSION` | account default | Optional; pin Stripe API version string if you need stability |
|
|
286
|
+
| `STUDIO_STRIPE_PRICE_INDIE` | — | Stripe **Price** id for Indie recurring plan |
|
|
287
|
+
| `STUDIO_STRIPE_PRICE_TEAM` | — | Stripe **Price** id for Small team recurring plan |
|
|
288
|
+
| `STUDIO_STRIPE_PRICE_MAP` | — | Optional `price_abc:indie,price_def:team` overrides / extra prices |
|
|
289
|
+
| `STUDIO_BILLING_SUCCESS_URL` | `/studio?billing=success` | Checkout success redirect (optional: add `{CHECKOUT_SESSION_ID}` per Stripe docs) |
|
|
290
|
+
| `STUDIO_BILLING_CANCEL_URL` | `/studio?billing=cancel` | Checkout cancel redirect |
|
|
291
|
+
| `STUDIO_BILLING_PORTAL_RETURN_URL` | `/studio` | Customer Portal return URL |
|
|
292
|
+
| `STUDIO_BLENDER_BIN` | — | Path to `blender` executable if not on `PATH` |
|
|
293
|
+
| `STUDIO_BLENDER_TIMEOUT_S` | `180` | Max seconds for headless Blender mesh export |
|
|
294
|
+
| `STUDIO_EXPORT_MESH_DEFAULT` | off | Set to `1` / `true` to run mesh export on every `run-job` when the client does not pass `export_mesh` |
|
|
295
|
+
| `DATABASE_URL` | — | Postgres connection string (e.g. Vercel Postgres, Neon). Required when using Postgres backends below. |
|
|
296
|
+
| `STUDIO_QUEUE_BACKEND` | `sqlite` | `postgres` (shared rows in Postgres), `redis` (Redis broker + hashes), or `sqs` (SQS long-poll + Postgres rows — needs `DATABASE_URL`). |
|
|
297
|
+
| `STUDIO_REDIS_URL` / `REDIS_URL` | — | Broker URL when `STUDIO_QUEUE_BACKEND=redis`. |
|
|
298
|
+
| `STUDIO_REDIS_QUEUE_ENGINE` | `zset` | With Redis queue: `zset` (sorted-set dispatch) or `streams` (`XADD` / `XREADGROUP` / `XACK`). |
|
|
299
|
+
| `STUDIO_REDIS_STREAMS_PREFIX` | `{STUDIO_REDIS_QUEUE_PREFIX}:sx` | Key namespace for Streams mode (isolated from zset keys). |
|
|
300
|
+
| `STUDIO_REDIS_STREAM_GROUP` | `studio_workers` | Redis consumer group name for Streams mode. |
|
|
301
|
+
| `STUDIO_REDIS_STREAM_BLOCK_MS` | `5000` | `XREADGROUP` block timeout (ms). |
|
|
302
|
+
| `STUDIO_REDIS_STREAM_MAXLEN` | `50000` | Approximate `MAXLEN` on the dispatch stream (`~`). |
|
|
303
|
+
| `STUDIO_REDIS_QUEUE_PREFIX` | `studio:q` | Key namespace for the Redis queue. |
|
|
304
|
+
| `STUDIO_REDIS_QUEUE_MAX_TIMELINE` | `5000` | Cap on timeline index entries (oldest trimmed). |
|
|
305
|
+
| `STUDIO_SQS_QUEUE_URL` | — | Standard or FIFO SQS queue URL when `STUDIO_QUEUE_BACKEND=sqs` (Postgres holds job metadata). |
|
|
306
|
+
| `STUDIO_SQS_WAIT_SECONDS` | `20` | SQS `ReceiveMessage` long-poll wait (max 20). |
|
|
307
|
+
| `STUDIO_SQS_VISIBILITY_TIMEOUT` | `900` | Visibility timeout for in-flight SQS messages (seconds). |
|
|
308
|
+
| `STUDIO_SQS_ENDPOINT_URL` | — | Optional (e.g. LocalStack). |
|
|
309
|
+
| `AWS_REGION` / `AWS_DEFAULT_REGION` | `us-east-1` | SQS client region when using `sqs` backend. |
|
|
310
|
+
| `STUDIO_TENANTS_BACKEND` | `sqlite` | Set `postgres` with `DATABASE_URL` for shared tenants / API keys / usage. |
|
|
311
|
+
| `STUDIO_JOB_ARTIFACTS` | `local` | `s3` (S3-compatible, incl. R2 via endpoint), or `vercel_blob` to upload `pack.zip` after each job. |
|
|
312
|
+
| `STUDIO_S3_BUCKET` | — | Bucket name when `STUDIO_JOB_ARTIFACTS=s3` (or `r2`). |
|
|
313
|
+
| `STUDIO_S3_REGION` | `auto` | AWS region; use `auto` for Cloudflare R2. |
|
|
314
|
+
| `STUDIO_S3_ENDPOINT_URL` | — | Custom endpoint URL (set for R2 / MinIO). |
|
|
315
|
+
| `STUDIO_S3_KEY_PREFIX` | `immersive-studio/packs` | Prefix for object keys (`{prefix}/{folder}/pack.zip`). |
|
|
316
|
+
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | — | Standard boto3 credentials for S3/R2 (or instance role). |
|
|
317
|
+
| `BLOB_READ_WRITE_TOKEN` | — | Vercel Blob store read-write token when `STUDIO_JOB_ARTIFACTS=vercel_blob`. |
|
|
318
|
+
|
|
319
|
+
**Optional pip extras:** `pip install -e ".[postgres]"` (`psycopg`), `pip install -e ".[redis]"` (`redis` client), `pip install -e ".[s3]"` (`boto3`), or `pip install -e ".[scale]"` (Postgres + Redis + S3 deps together).
|
|
320
|
+
|
|
321
|
+
## Stripe Billing (subscriptions → tiers)
|
|
322
|
+
|
|
323
|
+
Operations (monitoring, trial emails, optional notify URL): [docs/studio/stripe-monitoring-and-emails.md](../../docs/studio/stripe-monitoring-and-emails.md).
|
|
324
|
+
|
|
325
|
+
Python dependency: **`stripe`** (declared in `pyproject.toml`). Flow:
|
|
326
|
+
|
|
327
|
+
1. **Stripe Dashboard:** create **Products** with recurring **Prices** (e.g. monthly). Copy each **Price id** (`price_…`) into `STUDIO_STRIPE_PRICE_INDIE` / `STUDIO_STRIPE_PRICE_TEAM`, *or* set **`metadata.tier`** on the Price to `indie` / `team` / `free` (overrides id mapping).
|
|
328
|
+
2. **Enable Customer portal** (Billing → Customer portal) so `POST /api/studio/billing/portal-session` works.
|
|
329
|
+
3. **Webhooks:** add endpoint `https://<your-host>/api/studio/billing/webhook` and subscribe at minimum to:
|
|
330
|
+
- `checkout.session.completed`
|
|
331
|
+
- `customer.subscription.created`
|
|
332
|
+
- `customer.subscription.updated`
|
|
333
|
+
- `customer.subscription.deleted`
|
|
334
|
+
Paste the webhook **signing secret** into `STRIPE_WEBHOOK_SECRET`.
|
|
335
|
+
4. **Runtime env:** `STRIPE_SECRET_KEY`, `STUDIO_API_AUTH_REQUIRED=1`, tenant + API key as today.
|
|
336
|
+
5. **Indie group:** `tenants create` → `tenants issue-key` → open `/studio`, paste key → **Subscribe** (Checkout). After payment, webhooks link `stripe_customer_id`, set `stripe_subscription_id`, and call **`set_tenant_tier`** from the subscription’s price/metadata.
|
|
337
|
+
|
|
338
|
+
**Tier mapping rules:** For each subscription item’s Price, the worker uses (1) `metadata.tier` if valid, else (2) `STUDIO_STRIPE_PRICE_MAP` / env price ids, else (3) stays conservative (`free` if unknown). **Canceled / unpaid / incomplete_expired** subscriptions downgrade the tenant to **`free`**. **`past_due`** keeps the paid tier until Stripe resolves or cancels (adjust in `stripe_billing.py` if you prefer stricter cutoffs).
|
|
339
|
+
|
|
340
|
+
**Local testing:** [Stripe CLI](https://stripe.com/docs/stripe-cli) `stripe listen --forward-to localhost:8787/api/studio/billing/webhook` and use the printed **`whsec_…`** as `STRIPE_WEBHOOK_SECRET`.
|
|
341
|
+
|
|
342
|
+
**Manual override:** `immersive-studio tenants set-tier` still works for support / migrations; the next subscription webhook may overwrite tier until billing state matches.
|
|
343
|
+
|
|
344
|
+
## Indie / multi-tenant tiers (SaaS mode)
|
|
345
|
+
|
|
346
|
+
When **`STUDIO_API_AUTH_REQUIRED=1`**, each **workspace** (small studio / customer group) is a **tenant** with:
|
|
347
|
+
|
|
348
|
+
- **API keys** (hashed in SQLite — only shown once at `tenants issue-key`)
|
|
349
|
+
- **Monthly credits** (UTC calendar month), consumed by:
|
|
350
|
+
- `generate-spec`: **1** credit
|
|
351
|
+
- `jobs/run` or **queue enqueue** (sync cost up front): **2** credits, or **6** with **textures**
|
|
352
|
+
- **Concurrent job cap** (SQLite counter per tenant while `run_studio_job` is in flight)
|
|
353
|
+
- **Texture jobs** blocked on **Free** tier
|
|
354
|
+
|
|
355
|
+
Tiers are defined in `src/studio_worker/tiers.py` (`free`, `indie`, `team`). **Stripe webhooks** update `tier_id` from the active subscription; **`tenants set-tier`** remains a manual admin override.
|
|
356
|
+
|
|
357
|
+
**Per-tenant isolation:** job index entries, queue rows, and ad-hoc pack paths are tagged with `tenant_id` so one API key cannot list or download another workspace’s jobs.
|
|
358
|
+
|
|
359
|
+
Default **without** `STUDIO_API_AUTH_REQUIRED`: single-machine dev — unlimited dev tier, no keys (same as before).
|
|
360
|
+
|
|
361
|
+
## Blender placeholder mesh
|
|
362
|
+
|
|
363
|
+
Full pipeline reference (env, CI, Unity): [docs/studio/essentials.md](../../docs/studio/essentials.md).
|
|
364
|
+
|
|
365
|
+
Exports procedural placeholder geometry as `.glb` from a spec (placeholder until full kitbash lands):
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
blender --background --python path/to/site-packages/studio_worker/blender/export_mesh.py -- --spec path/to/spec.json --output path/to/out.glb
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Use `pack/spec.json` as the `--spec` argument when generating from a worker pack. Requires Blender 4.x on your `PATH`, or set **`STUDIO_BLENDER_BIN`**.
|
|
372
|
+
|
|
373
|
+
**Integrated path:** `immersive-studio run-job … --export-mesh`, **`POST /api/studio/jobs/run`** / queue with **`export_mesh: true`**, or the **Export placeholder mesh** checkbox on `/studio`. Successful runs place **`Models/<asset_id>/<asset_id>.glb`** and set **`manifest.toolchain.mesh_pipeline`** (`blender:export_mesh.py+ok` or `+error`). Failures are non-fatal: the job still completes with textures/spec; check **`mesh_logs`** in the API response or job metadata.
|
|
374
|
+
|
|
375
|
+
**Profiles:** `studio_worker/blender/export_mesh.py` picks a coarse shape from **`category`**: `prop` — stacked boxes with deterministic jitter; `environment_piece` — wide platform; `character_base` — two-block silhouette; `material_library` — small swatch cube; other categories fall back to a beveled cube scaled by **`target_height_m`**. PBR material names come from **`studio_worker/pbr_keys.py`** (same ordering as ComfyUI filenames and the Unity importer). The exported glTF material names are **`{variant_id}_{slot_id}`** per part so Unity can match them to **`{base}_Lit`** materials from textures.
|
|
376
|
+
|
|
377
|
+
## Unity import
|
|
378
|
+
|
|
379
|
+
Add the UPM package under [`../../packages/studio-unity`](../../packages/studio-unity) to a Unity 2022.3+ **URP** project, then **Immersive Labs → Import Studio Pack...** and select the folder that contains `manifest.json`.
|
|
380
|
+
|
|
381
|
+
## Layout
|
|
382
|
+
|
|
383
|
+
| Path | Role |
|
|
384
|
+
|------|------|
|
|
385
|
+
| `src/studio_worker/` | CLI, API, validation, jobs, ComfyUI client, texture pipeline, Stripe billing |
|
|
386
|
+
| `src/studio_worker/billing_config.py` | Env mapping: Stripe Price id → entitlement tier |
|
|
387
|
+
| `src/studio_worker/stripe_billing.py` | Webhook handlers, Checkout / Portal session builders |
|
|
388
|
+
| `src/studio_worker/billing_routes.py` | FastAPI routes under `/api/studio/billing/*` |
|
|
389
|
+
| `studio_worker/comfy_workflows/*.api.json` | ComfyUI `/prompt` graphs (SD1.5 + SDXL albedo v1) |
|
|
390
|
+
| `studio_worker/blender/export_mesh.py` | Headless export stub |
|
|
391
|
+
| `jobs/`, `queue.sqlite`, `tenants.sqlite`, `packs/…` | Writable state (default **`~/.immersive-studio/worker`** when `STUDIO_REPO_ROOT` is unset; otherwise **`apps/studio-worker/output/`** — gitignored in the monorepo) |
|
|
392
|
+
|
|
393
|
+
JSON Schema (runtime): `studio_worker/data/studio-asset-spec-v0.1.schema.json` (copy kept in sync with `packages/studio-types/schema/` in the monorepo).
|
|
394
|
+
|
|
395
|
+
### GPU / Comfy CI smoke test
|
|
396
|
+
|
|
397
|
+
Opt-in pytest marker `gpu_comfy` runs a minimal SD1.5 txt2img graph against live ComfyUI when:
|
|
398
|
+
|
|
399
|
+
- `STUDIO_RUN_GPU_COMFY_TESTS=1`
|
|
400
|
+
- `STUDIO_COMFY_URL` — base URL of ComfyUI
|
|
401
|
+
- `STUDIO_COMFY_CHECKPOINT` — checkpoint **filename** as shown in ComfyUI (SD1.5-style graph)
|
|
402
|
+
|
|
403
|
+
Optional: `STUDIO_COMFY_STEPS` (default `4`), `STUDIO_COMFY_TIMEOUT_S` (default `420`). Workflow: `.github/workflows/studio-worker-gpu-comfy.yml`.
|
|
404
|
+
|
|
405
|
+
**GitHub Actions — I can’t set these for you** (they live in your repo/org settings). In GitHub: **Settings → Secrets and variables → Actions → New repository secret**, create:
|
|
406
|
+
|
|
407
|
+
| Secret | Example value | Purpose |
|
|
408
|
+
|--------|----------------|---------|
|
|
409
|
+
| `STUDIO_RUN_GPU_COMFY_TESTS` | `1` | Enables the live pytest (otherwise it skips) |
|
|
410
|
+
| `STUDIO_COMFY_URL` | `http://127.0.0.1:8188` | Base URL of ComfyUI **as reachable from the workflow runner** |
|
|
411
|
+
| `STUDIO_COMFY_CHECKPOINT` | `your_model.safetensors` | Exact checkpoint name in ComfyUI’s loader |
|
|
412
|
+
|
|
413
|
+
`workflow_dispatch` can override the URL via the **comfy_url** input when non-empty. **GitHub-hosted runners** cannot see your LAN GPU; point `runs-on` at a **self-hosted** runner (or a URL the runner can reach) or run the test locally with the same env vars.
|