Skip to content
helmstudio
Contents

ltx studio's manifest

ltx studio makes video with sound from LTX-2.5 on MLX, behind a Python server with no front-end toolchain. Its registry entry carries its manifest inline. It passes five of the seven criteria a manifest can answer: it declares no test profile, and pins no SDK major. It declares no hue, so it is given one from helmstudio's fixed set.

The file is studios/ltx-studio.yaml, shown in full. A note follows the line it explains.

# A registry entry: a pointer at ltx studio's repository
# (schema/registry-entry.json, docs/design/05-sdk-and-custom-studios.md §5a).
#
# The manifest belongs in the studio's own repository as helmstudio.yaml, beside
# the code it describes, so that a studio can announce a new build step without
# waiting for a helmstudio release. ltx studio does not ship one yet, so the
# manifest is carried inline here — and the pull request that moves `ref` to a
# commit where it does ship one deletes the inline copy, leaving one copy again.
#
# Where the inline manifest sets id, repo or ref, each must equal this entry's;
# the validator enforces it. There is no `certified` field: a level is derived
# from what has actually been checked, never declared by the thing being
# checked (docs/decisions.md M7 Q4, Q15).

id: ltx-studio
The id the studio is known by everywhere, and the file is named for it.repo: https://github.com/janishar/ltx-2-studio
Where helmstudio clones from, with git.ref: bc82d75f0182e3631a6cbcd3b704e74445a32c5f
A full commit, so what was reviewed is what installs.
manifest:
Inline, because the repository ships no helmstudio.yaml at this commit. Its id, repo and ref must equal the pointer's.  id: ltx-studio
The same id as the pointer's.  name: ltx studio
The name people see.  description: MLX video and audio generation against a user-selected LTX checkpoint.
One sentence saying what the studio does.  kinds: [video, audio]
Video with sound.  license: MIT
The studio's own licence.  repo: https://github.com/janishar/ltx-2-studio
The same repository as the pointer's.  ref: bc82d75f0182e3631a6cbcd3b704e74445a32c5f
The same commit as the pointer's.  python: { version: "3.11" }
Python 3.11. helmstudio creates an environment with uv, pinned to this version, before the build step, and the studio runs with it active. No other studio shares it.  peak_ram_gb: 21 # README: 21 GiB at the validated default (short clips, int8 quantize-on-load); a 5s 1280x704 clip peaked ~76 GiB and swapped on the 64 GB test machine — one number cannot carry this range, see the report
The memory at the validated default. The comment says why one number cannot carry the range: a longer, larger clip peaked far higher.  requires:
An Apple Silicon Mac with 64 GB of memory and 90 GB of disk. uv is the one tool it needs; the build fetches everything else.    os: [darwin]
    arch: [arm64]
    tools: [uv]
    ram_gb: 64
    disk_gb: 90
  runtime:
MLX on Metal, quantised on load: Apple-native, like a Metal kernel, and a different claim from one.    framework: mlx
    backends: [metal]
    precision: [bf16, int8]
    language: python
  capabilities: [kv, assets, gallery, timeline]
Settings and sessions, the files it makes, the gallery and the timeline.  network: [huggingface.co, cdn-lfs.huggingface.co]
The hosts it is expected to contact, for its weights.  build:
One step: install exactly the locked dependencies, and fail rather than change the lock file in the checkout.    # --locked: install exactly uv.lock, and fail rather than rewrite it in the
    # checkout (M5 Q9).
    - name: Sync dependencies
      run: uv sync --locked --all-extras
  # Lightricks/LTX-2.5 is one repo of individually-named files, several marked
  # optional in the README's own table. `ltx` is the set the README's
  # "recommended" download command fetches (~71 GB); `ltx_dev_transformer`
  # unlocks two-stage/HQ/retake/extend and is the one the README prices
  # separately (~42 GB). Its exact path is inferred from the sibling
  # distilled-transformer file's naming pattern, not confirmed by a literal
  # download command in the README — flagged in the report.
  weights:
Three weights, two from one repository into one directory. The recommended set is required; the dev transformer and the Gemma encoder are optional, so install does not wait for them.    - name: ltx
      repo: Lightricks/LTX-2.5
      dest: LTX-2.5
      files:
        - diffusion_models/ltx-2.5-22b-distilled-transformer-bf16.safetensors
        - text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors
        - vae/ltx-2.5-video-vae-conv-bf16.safetensors
        - vae/ltx-2.5-audio-vae-bf16.safetensors
        - latent_upscale_models/ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0.safetensors
        - model_patches/ltx-2.5-duration-head-bf16.safetensors
      size_gb: 71
    - name: ltx_dev_transformer
      repo: Lightricks/LTX-2.5
      dest: LTX-2.5
      files: ["diffusion_models/ltx-2.5-22b-dev-transformer-bf16.safetensors"]
      size_gb: 42
      optional: true
    - name: gemma
      repo: mlx-community/gemma-3-12b-it-4bit
      dest: gemma-3-12b-it-4bit
      optional: true
  processes:
One process: the Python server in web/.    - name: studio
      role: main
      heavy: true
      cwd: web
The command runs in web/, relative to the checkout.      # Not `uv run`: it would sync the environment, and reach the network, at
      # every launch (M5 Q9). The environment is active, so python is its own.
      cmd: "python server.py --host 127.0.0.1 --port {port} --model {models.ltx}"
Plain python, not uv run, which would sync the environment and reach the network at every launch.      port: { prefer: 8720 }
      health: { tcp: true, timeout_s: 30, interval_s: 2 }
      # /api/queue answers a JSON list, not the busy contract (M5 Q12), so the
      # switch dialog reads ltx studio as unknown until ltx serves one; move this
      # path, and `ref`, when it does. Its renders run in their own session
      # (web/server.py:765) and it has no SIGTERM handler; helmstudio stops them
      # as detached descendants (M5 Q11), and a handler upstream is still wanted.
      busy: { path: /api/queue }
Declared, so the switch dialog asks before stopping it. Its answer is not the busy contract yet, so the dialog reads it as unknown, never as idle.      ui: /