openvibers/openvibe.studio README.md
README.md
| 1 | # OpenVibe.Studio |
| 2 | |
| 3 | Photo and Light: two open-source creative apps served inside an OpenVibe account. |
| 4 | |
| 5 | **Live site:** https://openvibe.studio · **API docs:** https://openvibe.services · **For AI:** https://openvibe.studio/llms.txt |
| 6 | **Run it:** `npm ci && npm run dev` (Node 22 or newer) · **Tests:** `npm test` |
| 7 | |
| 8 | - **Photo** is [PhotoCraft](https://github.com/storytold/photocraft), a layered photo editor. |
| 9 | - **Light** is [LightCraft](https://github.com/storytold/lightcraft), a photo library and RAW developer. |
| 10 | |
| 11 | The apps are the work of their own authors and are used under their own licences (MIT OR |
| 12 | Apache-2.0). We build them from pinned release tags, serve them under an OpenVibe sign-in, and |
| 13 | keep the licence and attribution with every build. See [ATTRIBUTION.md](ATTRIBUTION.md) and |
| 14 | [STATUS.json](STATUS.json). |
| 15 | |
| 16 | ## What is in here |
| 17 | |
| 18 | server/ the service: pages, the API, the adapters, the OpenVibe clients |
| 19 | public/js/ the browser side: one driver per app, and the layer around it |
| 20 | test/ the tests |
| 21 | scripts/ the bundle build and the first-frame smoke test |
| 22 | .github/workflows/ ci.yml (tests and the pinned security workflow) and bundle.yml (the builds) |
| 23 | |
| 24 | ## Running it |
| 25 | |
| 26 | npm ci |
| 27 | npm start # http://127.0.0.1:5155, the drill port is 15155 |
| 28 | |
| 29 | npm test # the tests |
| 30 | node scripts/smoke.js --app light --dir artifacts/light # the first-frame check |
| 31 | |
| 32 | The service serves the app bundles from `STUDIO_BUNDLE_ROOT` (by default `public/apps`). Without a |
| 33 | bundle the app page says so instead of showing an empty frame. |
| 34 | |
| 35 | ## The bundles |
| 36 | |
| 37 | A bundle is never kept in git. The `bundles` workflow checks out the pinned upstream tags, builds |
| 38 | them (Rust stable, `wasm32-unknown-unknown`, Trunk 0.21.14 for PhotoCraft, `cargo xtask web` and |
| 39 | wasm-bindgen 0.2.129 for LightCraft), runs the smoke test and publishes the result with a |
| 40 | checksum file. The deploy fetches that artifact, checks the checksums and unpacks it into the |
| 41 | bundle root. |
| 42 | |
| 43 | The smoke test is the gate: the build fails unless the app reaches its first frame in headless |
| 44 | Chrome. A bundle that loads but never starts is worse than no bundle. |
| 45 | |
| 46 | PhotoCraft is built by us on purpose. The published web release archive does not start: its loader |
| 47 | looks for the module under a path the archive does not have. |
| 48 | |
| 49 | ## The layer around an app |
| 50 | |
| 51 | Every app sits in an iframe inside our own page, with the OpenVibe navbar outside it. One adapter |
| 52 | per app keeps the same contract, so a base can be swapped: |
| 53 | |
| 54 | open(file) open a file from the person's OpenVibe library |
| 55 | save() hand an export to the person's OpenVibe.Download drive |
| 56 | commands() the app's own commands |
| 57 | |
| 58 | - **LightCraft** speaks the documented host bridge (`lightcraft:open`, `lightcraft:cmd`, |
| 59 | `lightcraft:save`) to a same-origin parent, so files open and exports come back. |
| 60 | - **PhotoCraft** has no scriptable API in the browser: a file is dropped on the app, and there is |
| 61 | no export the page can ask for. The app page says so. |
| 62 | |
| 63 | ## Hand-off from Video and Cam |
| 64 | |
| 65 | `GET /edit?media=<OpenVibe.Video id>&from=video` and `GET /edit?pics=<Pics slug>&from=cam` are the signed-in |
| 66 | hand-off into Studio. Studio asks the source service with the person's own Network token whether that person may |
| 67 | read the object; a private object of someone else answers 404 exactly as a missing id does. A picture opens in |
| 68 | Photo (or `app=light`) with its source named (`From OpenVibe.Cam`); the browser layer fetches the bytes through |
| 69 | `/api/v1/studio/files/pics/:slug/bytes` and puts them into the running app. A video opens in **Trim and publish**: the preview is at |
| 70 | `/api/v1/studio/media/:id/bytes`; two seconds fields and **Set start here** / **Set end here** take the time from the |
| 71 | player, **Use this frame as the cover** records the frame, and **Publish on Video** sends the range, cover, title and |
| 72 | visibility to Video's `/api/v1/video/studio/publish`. Video's existing cuts service re-encodes that range as a new |
| 73 | copy (the original is unchanged); the page polls `/api/v1/studio/publish/:cut` until the copy is ready. A range under |
| 74 | one second or outside the video is refused in plain words. |
| 75 | |
| 76 | Set `OV_VIDEO_URL` when Video is not at `https://openvibe.video` (the drill). The Studio routes are documented in |
| 77 | `server/pages.js` (`/edit`) and `server/api.js` (`/media/:id/bytes`, `/publish`). |
| 78 | |
| 79 | ## Honest limits |
| 80 | |
| 81 | - Heavy first load: both apps are native editors compiled to WebAssembly. |
| 82 | - A phone with 4 GB of RAM can run out of memory on big files: a 4 GB Photoshop file used 2.7-3.0 GB. |
| 83 | - Online only: neither app caches for offline use. |
| 84 | - Cut (FilmCraft) is not part of Studio v0. |
| 85 | |
| 86 | ## Licence |
| 87 | |
| 88 | The service is MIT licensed ([LICENSE](LICENSE)). The apps keep their own licences, and the |
| 89 | attribution is in [ATTRIBUTION.md](ATTRIBUTION.md). |
| 90 | |
| 91 | <!-- versions:start --> |
| 92 | - openvibe-contracts: v0.419.0 |
| 93 | - openvibe-sdk: v0.161.0 |
| 94 | - openvibe-shared: v4.241.0 |
| 95 | <!-- versions:end --> |
| 96 | |
| 97 | ### Pics image tools |
| 98 | |
| 99 | `/image-tools?picture=<slug>` opens an edit of a Pics picture. The authenticated |
| 100 | `POST /api/v1/studio/images/edit` API accepts `{picture, operation, prompt?}`; |
| 101 | operation is `upscale`, `remove-background`, or `restyle`. It reads authorized Pics |
| 102 | bytes and saves a separate private picture through Pics' re-encoding pipeline. |
| 103 | |
| 104 | Deployment requires `STUDIO_IMAGE_WORKER_URL`, an operator-controlled image worker |
| 105 | with `POST /edit` accepting `{operation, prompt, image: {mime_type, base64}}` and |
| 106 | returning JPEG, PNG or WebP bytes. No person token is sent to that worker; it receives |
| 107 | only the selected picture. The worker is not supplied by this PR. Without it the |
| 108 | capability endpoint reports unavailable, the UI disables submission and the API |
| 109 | returns 503. Tests use an in-process fake; no model calls or spending occurred. |
| 110 |