Metadata-Version: 2.4
Name: hugpy-video
Version: 0.3.14
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: flask>=2
Requires-Dist: pydantic>=2
Requires-Dist: abstract-essentials
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: hugpy-media>=0.3.9
Requires-Dist: hugpy-engine>=0.3.12
Requires-Dist: hugpy-oracle<0.4,>=0.3.18
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

Identity storage uses one shared implementation for HTTP handlers and render
jobs. See [identity storage](docs/IDENTITY_STORAGE.md) for transaction behavior,
deployment requirements, and the remaining HTTP wiring.
The [identity revival roadmap](docs/IDENTITY_REVIVAL.md) maps the archived
initialization, versions, cleanup, and 3D-continuity plans onto the current
packages, including staged controls and the remaining renderer-adapter work.
The [video handler inventory](docs/VIDEO_HANDLERS.md) lists current inference
operations, connection gaps, qualification requirements, and the wiring order
derived from the archived IDEA_PHASE lifecycle.

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](../../docs/arms.md). How engine-backed routes choose
and fall back between models is in
[docs/execute_prompt.md](../../docs/execute_prompt.md).


## 2026-10-21 studio overhaul (0.3.9)

Read `../docs/README.md` for the full index of findings and build notes. In short:

- **Real renders go through the wrapper engine** (`studio_i2v.render_clip` → `execute_prompt` →
  `{HUGPY_CORE_URL}/v1/videos/generations`), with honest phases (`queued`/`loading`/`rendering`/`done`),
  wrapper request-id correlation, 503 retry, load/stall deadlines, cancel → `orphaned`, and the
  placement verdict recorded on the job. Proven: Wan2.1-T2V-1.3B 480p/33f in ~45 s from the NVMe hot tier.
- **Legible backend state:** per-job JSONL event log (`<state>/job_logs/<job_id>.jsonl`), a full job view
  (`timeline`, `lifecycle`, `engine`, `placement`, `model_id`, `worker`, `log_tail`), `GET /video/status`,
  `hugpy-video jobs tail <id> [--follow]`, `hugpy-video status`, SSE events fixed (`HUGPY_VIDEO_EVENT_URL`,
  default self) and persisted across restarts.
- **Render templates:** `intel/studio/render_templates.json` (preference + tolerance + ordered stages per
  call type), `GET /video/studio/templates`, `GET /video/studio/template?template_id=`,
  `POST /video/studio/template/run`, `template_id` on `POST /video/studio/i2v`; executed contiguously as
  one `studio_template` job.
- **Render battery ledger:** `GET /video/battery` (HTML) / `GET /video/battery.json`,
  `hugpy-video battery [--since 24h] [--html PATH] [--json PATH]`; every real render also lands a row in the
  k71 model-battery root (`HUGPY_MODEL_BATTERY_ROOT`, live root `/srv/vm_mgr/share/projects/hugpy/model-battery`).
- **Text roles from the aptitude tests:** `intel/studio/studio_text_roles.json`, `GET /video/studio/roles`,
  `hugpy-video roles`; call sites default to the measured winners, request/env still win.
- **Registry/presets:** `RunnerSpec.ready` gates Err-stub runners, presets report live presence and refusal
  reasons, frame clamps carry a recorded reason, routing VRAM comes from the wrapper's `/v1/verdicts`.
- **UI:** unified Job Inspector, Media Viewer, render template picker; the embedded bundle is a local
  0.3.11 build (see `src/hugpy_video/ui_dist/PROVENANCE.md`).

Testing note: never run the suite with `HUGPY_CORE_URL` pointing at a live wrapper or core — several tests
call the render path for real. Use `HUGPY_CORE_URL=http://127.0.0.1:9` (and `HUGPY_FITEVICT_FRONT_DOOR`), and do not
export `HUGPY_VIDEO_STATE_DIR` / `DEFAULT_ROOT` (conftest isolates storage per process). See `../docs/README.md`.

## Dependency on hugpy-oracle

**`hugpy-oracle` is a hard dependency** (`hugpy-oracle>=0.3.11,<0.4` in 0.3.5;
`>=0.3.10` in 0.3.4; operator ruling 2026-10-15). 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.11,<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/video_intelligence_ui`](../../ui/video_intelligence_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>

