Metadata-Version: 2.4
Name: hugpy-video
Version: 0.3.20
Summary: Hugpy video: media library and job bus, ffmpeg, synthetic and studio runners, reservations and studio sessions for video intelligence
Author-email: putkoff <support@hugpy.ai>
License-Expression: LicenseRef-Proprietary
Project-URL: Homepage, https://hugpy.ai
Project-URL: Documentation, https://github.com/hugpy/hugpy/blob/main/py/cinema/hugpy_video/README.md
Project-URL: Repository, https://github.com/hugpy/hugpy
Project-URL: Source, https://github.com/hugpy/hugpy/tree/main/py/cinema/hugpy_video
Project-URL: Issues, https://github.com/hugpy/hugpy/issues
Project-URL: Changelog, https://github.com/hugpy/hugpy/releases
Project-URL: Architecture, https://github.com/hugpy/hugpy/blob/main/PARTITION.md
Keywords: hugpy,llm,self-hosted,video,ffmpeg,video-generation,media-intelligence
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: hugpy-resources<0.2,>=0.1.2.8
Requires-Dist: flask>=2
Requires-Dist: pydantic>=2
Requires-Dist: abstract-essentials
Requires-Dist: abstract_utilities>=0.2.2.794
Requires-Dist: requests>=2.28
Requires-Dist: Pillow
Requires-Dist: abstract-flask>=0.0.0.1071
Requires-Dist: hugpy-platform>=0.3.0
Requires-Dist: abstract_database>=0.0.2.202
Requires-Dist: hugpy-media>=0.3.9
Requires-Dist: hugpy-engine>=0.3.9
Requires-Dist: hugpy-oracle<0.4,>=0.3.15
Provides-Extra: render
Requires-Dist: numpy; extra == "render"
Requires-Dist: Pillow; extra == "render"
Requires-Dist: requests; extra == "render"
Provides-Extra: studio
Requires-Dist: torch; extra == "studio"
Requires-Dist: diffusers; extra == "studio"
Requires-Dist: transformers; extra == "studio"
Requires-Dist: numpy; extra == "studio"
Requires-Dist: Pillow; extra == "studio"
Requires-Dist: opencv-python-headless; extra == "studio"
Requires-Dist: imageio; extra == "studio"
Requires-Dist: imageio-ffmpeg; extra == "studio"
Requires-Dist: requests; extra == "studio"
Provides-Extra: identity
Requires-Dist: abstract-identity; extra == "identity"
Requires-Dist: requests; extra == "identity"
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: pytest-timeout; extra == "test"
Dynamic: license-file

# hugpy-video

The staged HTTP arm includes Oracle as a child Blueprint of `video_bp`.
Core mounts this one Blueprint; `/api/oracle/*` and the full
`/api/video/script/*` REST API run in the same process as Video. The standalone
Video app mounts the same composite Blueprint. The `hugpy.arms` entry point
exports `hugpy_video.routes:video_bp`; dictionary/tool consumers may still use
`categories.ALL_CATEGORIES`. Its legacy script adapters are excluded from HTTP
registration so Oracle's native methods, status codes, and error bodies own
those resources without overlapping routes. This requires Oracle 0.3.12+,
which no longer publishes a separate `hugpy.arms` entry point.

The **video arm** of Hugpy (import name `hugpy_video`). The source version is
declared in `pyproject.toml`; `/api/video/health` reports the running package.
It covers execution and artifacts for video work:

* the media library jail and the sqlite **media job bus**, with its local
  runners (ffmpeg crop, frames and audio);
* engine-backed generation (image, scene and movie), the studio spine (t2v/i2v
  clips, segmented movies, the model battery) and the GPU reservation engine;
* the identity-profile library with relays to the identity render service;
* LLM prompt assist;
* the `/video` Video Intelligence console (SPA) and its API
  (`/api/video/*`, `/api/movie/*`), mounted inside Core through its arm hooks
  or served by the standalone Video service.

The arm contract (toolset → Blueprint → standalone server → `arms.d` → core
proxy) is in [docs/arms.md](../../hugpy-core/package/docs/ROUTES-CONVENTION.md). How engine-backed routes choose
and fall back between models is in
[docs/execute_prompt.md](../../docs/execute_prompt.md).


## Dependency on hugpy-oracle

**`hugpy-oracle` is a required dependency**; its version range is declared in
`pyproject.toml`. The dependency graph runs
**video → oracle**. The oracle's use of video is lazy and optional: its `video` extra pulls in `hugpy-video`, and
it reaches video's studio registry, runners and media bus only inside the
functions that need them. Video imports the oracle **lazily**, inside the
toolset callables that need it (`studio/plan`, `producer/plan`,
`performance/probe`, `script/runs`, `script/sources`), so `import hugpy_video`
loads no planner. The oracle's planners install into video's hooks
(`hugpy_video.bus.hooks`) through `hugpy_oracle.install_hooks()`. That call is
made idempotently by the planner routes, and by the `hugpy.video.hooks` entry
point when `hugpy_video.web.api` loads.

## Layout

```
src/hugpy_video/
  imports/        the shared layer: imports no optional or heavy backend
    constants.py  ARM_NAME="video", UI_PREFIX="/video/", every env-var NAME and default
    functions.py  env_value()/env_bool(), parse_env_file(), lazy()/first_lazy()
    classes.py    VideoError, ToolError (the toolset error, which becomes {"error": ...}),
                  JobError (the job error-as-data, lifted from hugpy_control in 0.3.5)
    store.py      stdlib cross-process sqlite helpers, lifted from hugpy_control in 0.3.5
                  (used by the studio-assist log and the reservation ledger; same env
                  names and paths, so existing stores keep working)
    init_imports.py, __init__.py
  toolset.py      get_toolset() -> {"video": {...}, "movie": {...}}, get_manifest()
  routes.py       video_bp = register_categories(get_bp("video"), get_toolset())
                  plus the native SSE escape hatches
  serve/          the standalone service: `hugpy-video serve` (app.py: create_app, serve)
  ui.py           dist_dir() -> ui_dist/ (built @hugpy/video-intelligence-ui)
  web/            the Flask view implementation the toolset adapts: api (read surface,
                  presets, identity library, media/clip bytes), generate, identity_write,
                  movie_routes, assist, studio_planning, render_catalog, jobstream (SSE),
                  studio_assist_log, video_intent
  bus/            the job-bus control plane: jobs (public API, register_job), job_events
                  (worker -> core transitions), hooks (PromptCoordinator and planner seams)
  intel/          media_store (refs and jail), media_bus (sqlite bus), job_bridge,
                  job_schema, presets, reservation/, runners/ (ffmpeg, diffusers plane,
                  studio, identity relays, MLT), studio/ (the studio spine), plane.py
                  (the one seam onto hugpy_engine.execute_prompt)
  chat_video/     chat-side frame analysis over a hugpy_media vision runner
  video_gen/      StudioVideoRunner / VideoGenRequest (engine runner for t2v/i2v)
  plugin.py       hugpy_engine.tasks entry point: text-to-video, image-to-video
  state.py        injectable state roots;  config.py  HUGPY_API_KEY / HUGPY_BASE loader
  cli.py          `hugpy-video`;  selftest.py;  sql/001_video_jobs_notify.sql
  _lifted/        helpers lifted from the retired monolith
```

Import HTTP modules directly from `hugpy_video.web` and job-bus modules from
`hugpy_video.bus`. The old top-level aliases have been removed. Wildcard imports
are forbidden (`tests/test_import_policy.py`).

## The toolset (`video/*` 56 routes, `movie/*` 2 routes)

Each route is `GET`+`POST` at `/api/<category>/<route>`, with a trailing-slash
variant. Ids are kwargs (`?job_id=`, `?slug=`, `?preset_id=`, `?movie_id=`), not
path segments. The return value comes back as `{"result": ...}` and an error as
`500 {"error": "<real message>"}`. Byte routes return the file `Response`
directly (status 200, 206 or 404 preserved). Source: `toolset.get_toolset()`,
verified against the live `GET /api/video/manifest`.

### Works standalone (no core, no GPU)

| Route | Notes |
|---|---|
| `health`, `manifest` | identity; the manifest includes `escape_hatches` |
| `jobs` | job bus listing; reads `?all`, `?stale`, `?limit`, `?owner` |
| `job`, `job/cancel`, `job/private` | `job_id`; `job/private` reads `private` from the body |
| `media` | `handle`: raw bytes for a jailed MediaRef (Range supported) |
| `projects`, `presets` | catalog reads |
| `studio/clips`, `studio/clip`, `studio/clip/detail`, `studio/clip/archive`, `studio/clip/unarchive` | `job_id`; `studio/clip` streams the mp4. Archive never deletes. |
| `studio/presets`, `studio/presets/apply` | `preset_id` |
| `identity-profiles`, `identity-profile`, `identity-profile/delete`, `identity-profile/private` | `slug`; delete archives, never hard-deletes |
| `ingest` | turns a path under the video storage root into a MediaRef |
| `jobs/crop`, `jobs/frame_extract`, `jobs/audio_extract` | ffmpeg runners on the local bus (needs `ffmpeg`; the deb depends on it) |
| `prompt/assist/log`, `prompt/assist/ticket` (`ticket`), `studio/movies` | local reads |
| `performance/probe` | the oracle's `(oracle, performance)` readiness, reported as data: `ready`, `bound`, `unbound` with the operator step for each gap |
| `script/sources`, `script/runs` | the oracle's script-first run store. `GET script/runs` lists runs; `POST` starts one. |
| `movie/presets`, `movie/presets/apply` | `preset_id` |

### Needs a reachable core and GPU fleet

Each of these first checks `HUGPY_CORE_URL` (default `http://127.0.0.1:7020`,
1.5 s). If nothing answers, the route raises `ToolError` naming the variable and
`/etc/hugpy/video.env`, and does not enqueue anything. The check proves only
that something answers HTTP at that URL; the fleet itself is reached later,
through `execute_prompt`.

`jobs/generate_image`, `jobs/generate_scene`, `jobs/generate_movie`,
`studio/i2v`, `studio/tester`, `studio/movie`, `studio/movie/pause`
(`movie_id`), `studio/movie/resume` (`movie_id`), `studio/plan` and
`producer/plan` (oracle-backed planners), `prompt/assist`, `prompt/intent`,
`prompt/assist/models`, `render/presets`.

### Needs the identity render service

These require `IDENTITY_RENDER_URL` (and `IDENTITY_RENDER_TOKEN`). Without it
they raise a `ToolError` that names the variables:

`identity-profiles/create`, `identity-profiles/from-video`,
`identity-profiles/from-images`, `identity-profiles/video-extract`,
`identity-profile/reconstruction` (`slug`), `identity-profile/canonical`
(`slug`), `identity-profile/generate` (`slug`). The `identity` extra installs
the `abstract-identity` client. It does not start a GPU service.

### Stubs: reachable, not implemented in this build

These always answer `500 {"error": "<route> is not implemented in this
hugpy-video build: <what it needs>"}`. They never return 404:

| Route | Missing piece |
|---|---|
| `identity-render/probe` | the identity render service probe |
| `studio/clip/to-editor` | the Filmora editor handoff (ffprobe/remux) |
| `identity-profile/settings` | the identity `gen_settings` write (not ported) |
| `identity-profile/update` | the identity profile name/notes write (not ported) |
| `identity-profile/versions` | identity version management (not ported) |

### Escape hatches (native Blueprint routes)

| Route | Kind |
|---|---|
| `GET /api/video/jobs/stream` | SSE. Sends a `stream.ready` marker, then one row per job transition, filtered to what the caller may see. Capped at one hour; the client re-seeds from `video/jobs`. |
| `GET /api/video/prompt/assist/log/stream` | SSE: the studio assist log |
| `POST /api/video/jobs/event` | internal ingest: a worker reports `{job_id, status}`. Returns 202. Not listed in `escape_hatches`. |

**The job stream is single-process by default.** With
`HUGPY_VIDEO_JOBS_PG_DSN` unset, a reported transition goes straight into this
process's in-memory event ring and no Postgres listener starts. Set it (for
example `dbname=hugpy_video`) only when several processes share one bus and
need Postgres `LISTEN/NOTIFY` to fan events out. That needs
`sql/001_video_jobs_notify.sql` and `psycopg`. If Postgres is then unreachable,
the listener retries and the stream degrades to the initial seed.

### Not served (pending)

* **`POST /video/jobs/performance`.** The Oracle station's performance submit
  is not in the toolset, and the standalone service returns 404. The legacy
  `web/api.py` registers it only as a "501 not available" placeholder. Use
  `performance/probe` to see which seams are unbound.
* **Video UI uploads.** The SPA's file stations (image crop, audio crop, frame
  extract, identity upload, studio generate) upload to `/api/uploads`. Neither
  this service nor core 0.4.40 serves that route (404).

## Standalone service

```bash
hugpy-video serve [--host H] [--port P] [--env-file /etc/hugpy/video.env]
```

`create_app()` does the following:

* loads the env file without overriding the environment;
* creates the state directories and initialises the sqlite job bus;
* builds `abstract_flask.get_Flask_app(name="hugpy-video",
  routes=hugpy_video.routes, url_prefix="api")`;
* serves the SPA at `/video/` with history fallback. `/` and `/video` redirect
  to `/video/`. A build without `ui_dist` serves only the API.
* forwards `/v1/*` (streamed) to `HUGPY_CORE_URL`. This exists because the
  UI's model pickers read core's `/v1/models` and `/v1/task-bindings`. With core
  unreachable it answers `503 {"error": "hugpy core unreachable at ..."}`.

Then it runs Flask threaded, so SSE does not block other requests.

**Demo media.** The served `index.html` marker
`window.__HUGPY_MEDIA_BASE__ = window.__HUGPY_MEDIA_BASE__ ?? null;` is
rewritten from the environment. `HUGPY_DEMO_MEDIA_DIR` (served by this process
at `/demo-media/`) takes precedence over `HUGPY_DEMO_MEDIA_BASE` (a URL).

```bash
curl -s 127.0.0.1:7341/api/video/health      # {"result":{"arm":"video","ok":true,"version":"0.3.4"}}
curl -s '127.0.0.1:7341/api/video/jobs?limit=5'
curl -s 127.0.0.1:7341/api/video/performance/probe
curl -sN 127.0.0.1:7341/api/video/jobs/stream
```

### Merged into core

The arm also runs in-process inside `hugpy-core` (operator decision 2026-10-21;
`docs/CORE-MERGE-PLAN.md`). Core does **not** run `serve`/`app.run`, does **not**
read `/etc/hugpy/video.env`, and does **not** mount the `/v1` forwarder or the
`/` redirect. Instead it loads two entry points by group name:

* `hugpy.arm_prepare` → `serve.embed:prepare()` — creates the state tree +
  sqlite job bus and starts the media_bus runner pool and the
  `hugpy.video.hooks` planners in the core process.
* `hugpy.arm_ui` → `serve.embed:wrap_ui()` — serves the built SPA at `/video/`
  (history fallback, demo-media marker rewrite) as a pure-WSGI wrapper.

Core mounts the routes explicitly (`from hugpy_video.routes import video_bp` in
`hugpy_core.routes`) and keeps `arms_group` disabled so media/oracle are not
auto-mounted (see ROUTES-CONVENTION.md). Under core the config is a single
root-only env source (`/etc/hugpy/core.env`), not `video.env`:

| Variable | Value under core | Why |
|---|---|---|
| `DEFAULT_ROOT` | `/var/lib/hugpy-core/root` | state resolves to `<DEFAULT_ROOT>/video_intel`; keeps `/video_intel/` in artifact paths for the guest jail |
| `HUGPY_VIDEO_STATE_DIR`, `HUGPY_MEDIA_JOBS_DB`, `STUDIO_OUTPUT_ROOT`, `HUGPY_RESERVATIONS_DB`, `UPLOADS_HOME`, `PROJECTS_HOME`, `IDENTITIES_HOME` | unset | compose from `DEFAULT_ROOT`; do **not** reuse `/var/lib/hugpy-video` |
| `HUGPY_VIDEO_EVENT_URL` | `http://<core-host>:<core-port>/api` (core itself) | the HTTP job-event fallback posts to core's own `/api`; the default dead `127.0.0.1:7341` is never used |
| `HUGPY_CORE_URL` | `http://<core-host>:<core-port>` (self) | engine preflight + `/v1/videos/generations` relay to the model-holding box |
| `HUGPY_AUTH_MODE` | `open` | every caller nginx admits is the operator (matches the console) |
| `HUGPY_VIDEO_JOBS_PG_DSN` | unset | single-process ring (core is one werkzeug process) |
| `STUDIO_WEIGHTS_ROOT`, `HF_HUB_OFFLINE=1`, `HUGPY_MODEL_BATTERY_ROOT` | as in the plan | presence/routing, offline HF probes, battery ledger |
| `HUGPY_VIDEO_EMBED_DAEMON` | unset (= start pool) | set `0` for a control-plane-only profile: routes + SPA, no runner threads |

## CLI

| Command | What it does |
|---|---|
| `hugpy-video serve [...]` | the standalone service |
| `hugpy-video jobs list [--all] [--limit N] [--json]` | the media job bus |
| `hugpy-video jobs registry` | registered job names, runner keys, queues and timeouts |
| `hugpy-video selftest` | in-process checks (no GPU, no service); exits 1 on failure |
| `hugpy-video state` | prints the resolved state roots |
| `hugpy-video models audit [--json]` | declared studio models: runner gaps, weight pin, VRAM by precision. Loads no weights. |
| `hugpy-video --version` | |

## Environment

The names are defined in `imports/constants.py`.

| Variable | Default | Effect |
|---|---|---|
| `HUGPY_VIDEO_HOST` / `HUGPY_VIDEO_PORT` | `127.0.0.1` / `7341` | bind address of `serve` |
| `HUGPY_VIDEO_STATE_DIR` | `<DEFAULT_ROOT>/video_intel` (the deb uses `/var/lib/hugpy-video`) | job bus and studio sessions |
| `HUGPY_MEDIA_JOBS_DB` | `<state>/media_jobs.db` | sqlite job bus |
| `HUGPY_RESERVATIONS_DB` | `<PROJECTS_HOME>/reservations.db` | GPU reservation ledger |
| `STUDIO_OUTPUT_ROOT` | `<state>/studio` | studio sessions and renders |
| `DEFAULT_ROOT`, `UPLOADS_HOME`, `IDENTITIES_HOME`, `PROJECTS_HOME` | `hugpy_platform` (the deb sets `DEFAULT_ROOT=/var/lib/hugpy-video/root`) | platform storage roots; the media jail is `UPLOADS_HOME` plus `DEFAULT_ROOT` |
| `HUGPY_CORE_URL` | `http://127.0.0.1:7020` | engine preflight, `/v1` forwarder, engine relays |
| `HUGPY_VIDEO_JOBS_PG_DSN` | unset (single-process) | multi-process job-stream fan-out |
| `IDENTITY_RENDER_URL`, `IDENTITY_RENDER_TOKEN` | unset | identity render service |
| `HUGPY_DEMO_MEDIA_DIR`, `HUGPY_DEMO_MEDIA_BASE` | unset | demo-media marker rewrite |
| `HUGPY_WORKER_NAME` | unset | worker identity on reported job events |
| `HUGPY_API_KEY`, `HUGPY_BASE` | `""`, `https://dev.hugpy.ai/api` | `config.load_config()` credentials. The legacy aliases `CONSOLE_API`, `HUGPY_API_TOKEN`, `HUGPY_AGENT_API`, `HUGPY_API_KEY_NEWVM` / `HUGPY_BASE_URL`, `HUGPY_URL`, `HUGPY_CENTRAL` are accepted and recorded in `Config.deprecations`. |

Tuning knobs read by the runners include `HUGPY_MEDIA_BUS_*` (runners,
lookahead, stale and reap timing), `HUGPY_RESERVATION*`, `STUDIO_WEIGHTS_ROOT`,
`STUDIO_MAX_VRAM_GB`, `STUDIO_ALLOW_UNPINNED` and `HUGPY_SCENE_CONCURRENCY`.
Their defaults live next to the code that reads them.

## Debian package

```bash
BUILD_PYTHON=/usr/bin/python3 packaging/debian/build.sh     # -> build/debian/hugpy-video_<ver>_amd64.deb (Python 3.12)
sudo apt install ./build/debian/hugpy-video_<ver>_amd64.deb      # 0.3.4 is the newest deb built so far
```

`build.sh` bundles offline wheels for `hugpy-video` and its local siblings
(`hugpy-oracle`, `hugpy-platform`, `hugpy-media`, `hugpy-engine`) and their
public dependencies. The 0.3.4 deb also bundled `hugpy-control` and
`hugpy-storage`. Neither is needed since video 0.3.5, oracle 0.3.11 and
media 0.3.4. `build.sh` asserts that the wheel contains `ui_dist/index.html`
and `sql/001_video_jobs_notify.sql`.
The control file declares `Depends: python3 (>= 3.12), python3 (<< 3.13),
python3-venv, systemd, ffmpeg`.

`postinst`:

* builds `/opt/hugpy-video/venv` offline and force-reinstalls the bundled
  siblings;
* creates the `hugpy-video` system user, a member of the `hugpy` group;
* creates `/var/lib/hugpy-video` (mode 0750) with `root/` inside it;
* writes `/etc/hugpy/video.env` if it is absent;
* writes `hugpy-video.service` (`WorkingDirectory=/var/lib/hugpy-video`,
  `ProtectSystem=strict`, `ReadWritePaths` set to the state directory);
* writes `/usr/share/hugpy/arms.d/video.json` (categories `video` and `movie`,
  UI `/video/`; the upstream port follows `video.env`);
* restarts the service.

Core then proxies `/api/video/*`, `/api/movie/*` and `/video/*`. `postrm`
removes the unit and the `arms.d` file. On purge it also removes the venv, the
state, `video.env` and the user. Host-level steps are in
[docs/install.md](../../docs/install.md).

## Dependencies

Runtime (`pyproject.toml`, 0.3.5): `flask>=2`, `pydantic>=2`,
`abstract-essentials`, `requests>=2.28`, `abstract-flask>=0.0.0.1052`,
`hugpy-platform>=0.3.0`, `hugpy-media>=0.2.8`, `hugpy-engine>=0.3.2`,
`hugpy-oracle>=0.3.15,<0.4`.

`hugpy-control` is no longer a dependency as of 0.3.5. Its sqlite store helpers
and `JobError` now live in `hugpy_video.imports` (`store.py`, `classes.py`).
Version 0.3.4 depended on `hugpy-control>=0.3.0`.

Extras: `render` (numpy, Pillow, requests), `studio` (torch, diffusers,
transformers and the rest of the GPU set), `identity` (abstract-identity),
`test`. GPU and runtime pieces are never imported at module scope.

## Entry points

| Group | Name → target |
|---|---|
| `console_scripts` | `hugpy-video` → `hugpy_video.cli:main` |
| `hugpy.arms` | `video` → `hugpy_video.routes:video_bp` |
| `hugpy.toolsets` | `video` → `hugpy_video.toolset:get_toolset` |
| `hugpy_engine.tasks` | `video` → `hugpy_video.plugin:register` (`text-to-video`, `image-to-video`) |

## Rules

* Heavy imports are lazy. The package must import on a CPU-only box.
* The version is single-sourced from `pyproject.toml` through
  `importlib.metadata`.
* Credentials come only through `hugpy_video.config.load_config`.

## UI

The SPA source is [UI source](../ui/README.md).
Its build is copied into `src/hugpy_video/ui_dist/`, and the provenance is in
`ui_dist/PROVENANCE.md`.


<!-- hugpy-public-downloads-gallery -->
## Downloads and feature gallery

Get the Debian packages, architecture details, SHA-256 checksums and installation commands from [Hugpy Downloads](https://hugpy.ai/downloads/). The [feature gallery](https://hugpy.ai/downloads/#features) shows the console, Agent, Station, Media and Video interfaces. Screenshots reflect the versions captured; the current interface may differ.

<details>
<summary>Video studio</summary>

A clip viewer, shot staging, model settings and generation controls.

![Video studio](https://hugpy.ai/downloads/gallery/hugpy/hugpy-video.png)

</details>


## Import convention

Video follows Discovery's import boundary. Standard-library names and external
package owners are declared in `hugpy_video/imports/init_imports.py`. Package
modules import them from that boundary; Video-internal imports are relative.
The existing `imports/constants.py`, `imports/classes.py`, and
`imports/functions.py` retain their responsibilities and shared public exports.

Optional dependencies (including Core's inference services and GPU backends)
resolve through the shared Resources import registry only when requested.
Function-local imports remain local so unavailable optional features do not
prevent package startup. Distinct implementations with the same symbol name
have separate boundary aliases. Legacy dotted imports preserve their parent
module binding, such as `urllib.request` exposing `urllib` to its caller.

The registry requires `hugpy-resources>=0.1.2.7`. Source edits do not install or
publish either package. Run `python -m pytest tests/test_import_policy.py` with
Video and Resources installed, or with their source directories on `PYTHONPATH`.

## Isolated development checks

Video tests can exercise rendering. Use isolated test storage and a non-live
Core/Wrapper endpoint; consult the test fixtures before running them.
