Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
python-embed
dist
dist-electron
release
backend/.venv
backend/outputs
backend/models
.git
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ jobs:
runner: macos-latest
- os: windows
runner: windows-latest
- os: linux
runner: ubuntu-latest
defaults:
run:
working-directory: backend
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ LTX Desktop is an Electron app for AI video generation using LTX models. Three-l
| `pnpm typecheck:py` | Python pyright only |
| `pnpm backend:test` | Run Python pytest tests |
| `pnpm build:frontend` | Vite frontend build only |
| `pnpm build:mac` / `pnpm build:win` | Full platform builds |
| `pnpm setup:dev:mac` / `pnpm setup:dev:win` | One-time dev environment setup |
| `pnpm build` | Full platform build (auto-detects platform) |
| `pnpm setup:dev` | One-time dev environment setup (auto-detects platform) |

Run a single backend test file via pnpm: `pnpm backend:test -- tests/test_ic_lora.py`

Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LTX Desktop

LTX Desktop is an open-source desktop app for generating videos with LTX models — locally on supported Windows NVIDIA GPUs, with an API mode for unsupported hardware and macOS.
LTX Desktop is an open-source desktop app for generating videos with LTX models — locally on supported Windows/Linux NVIDIA GPUs, with an API mode for unsupported hardware and macOS.

> **Status: Beta.** Expect breaking changes.
> Frontend architecture is under active refactor; large UI PRs may be declined for now (see [`CONTRIBUTING.md`](docs/CONTRIBUTING.md)).
Expand Down Expand Up @@ -32,8 +32,9 @@ LTX Desktop is an open-source desktop app for generating videos with LTX models
| --- | --- | --- |
| Windows + CUDA GPU with **≥32GB VRAM** | Local generation | Downloads model weights locally |
| Windows (no CUDA, <32GB VRAM, or unknown VRAM) | API-only | **LTX API key required** |
| Linux + CUDA GPU with **≥32GB VRAM** | Local generation | Downloads model weights locally |
| Linux (no CUDA, <32GB VRAM, or unknown VRAM) | API-only | **LTX API key required** |
| macOS (Apple Silicon builds) | API-only | **LTX API key required** |
| Linux | Not officially supported | No official builds |

In API-only mode, available resolutions/durations may be limited to what the API supports.

Expand All @@ -46,6 +47,14 @@ In API-only mode, available resolutions/durations may be limited to what the API
- 16GB+ RAM (32GB recommended)
- **160GB+ free disk space** (for model weights, Python environment, and outputs)

### Linux (local generation)

- Ubuntu 22.04+ or similar distro (x64 or arm64)
- NVIDIA GPU with CUDA support and **≥32GB VRAM** (more is better)
- NVIDIA driver installed (PyTorch bundles the CUDA runtime)
- 16GB+ RAM (32GB recommended)
- Plenty of free disk space for model weights and outputs

### macOS (API-only)

- Apple Silicon (arm64)
Expand All @@ -64,6 +73,7 @@ LTX Desktop stores app data (settings, models, logs) in:

- **Windows:** `%LOCALAPPDATA%\LTXDesktop\`
- **macOS:** `~/Library/Application Support/LTXDesktop/`
- **Linux:** `$XDG_DATA_HOME/LTXDesktop/` (default: `~/.local/share/LTXDesktop/`)

Model weights are downloaded into the `models/` subfolder (this can be large and may take time).

Expand All @@ -84,7 +94,7 @@ The LTX API is used for:
- API-based video generations (required on macOS and on unsupported Windows hardware) — paid
- Retake — paid

An LTX API key is required in API-only mode, but optional on Windows local mode if you enable the Local Text Encoder.
An LTX API key is required in API-only mode, but optional on Windows/Linux local mode if you enable the Local Text Encoder.

Generate a FREE API key at the [LTX Console](https://console.ltx.video/). Text encoding is free; video generation API usage is paid. [Read more](https://ltx.io/model/model-blog/ltx-2-better-control-for-real-workflows).

Expand Down Expand Up @@ -137,11 +147,7 @@ Prereqs:
Setup:

```bash
# macOS
pnpm setup:dev:mac

# Windows
pnpm setup:dev:win
pnpm setup:dev
```

Run:
Expand Down
1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies = [
"uvicorn[standard]>=0.30.0",
"python-multipart>=0.0.9",
"triton-windows; sys_platform == 'win32'",
"triton; sys_platform == 'linux'",
]

[[tool.uv.index]]
Expand Down
2 changes: 1 addition & 1 deletion backend/runtime_config/runtime_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def decide_force_api_generations(system: str, cuda_available: bool, vram_gb: int
if system == "Darwin":
return True

if system == "Windows":
if system in ("Windows", "Linux"):
if not cuda_available:
return True
if vram_gb is None:
Expand Down
18 changes: 17 additions & 1 deletion backend/tests/test_runtime_policy_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,21 @@ def test_windows_with_required_vram_allows_local_mode() -> None:
assert decide_force_api_generations(system="Windows", cuda_available=True, vram_gb=31) is False


def test_linux_without_cuda_forces_api() -> None:
assert decide_force_api_generations(system="Linux", cuda_available=False, vram_gb=24) is True


def test_linux_with_low_vram_forces_api() -> None:
assert decide_force_api_generations(system="Linux", cuda_available=True, vram_gb=30) is True


def test_linux_with_unknown_vram_forces_api() -> None:
assert decide_force_api_generations(system="Linux", cuda_available=True, vram_gb=None) is True


def test_linux_with_required_vram_allows_local_mode() -> None:
assert decide_force_api_generations(system="Linux", cuda_available=True, vram_gb=31) is False


def test_other_systems_fail_closed() -> None:
assert decide_force_api_generations(system="Linux", cuda_available=True, vram_gb=48) is True
assert decide_force_api_generations(system="FreeBSD", cuda_available=True, vram_gb=48) is True
2 changes: 2 additions & 0 deletions backend/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ Prereqs:
Setup:

```bash
# macOS
pnpm setup:dev:mac

# Windows
pnpm setup:dev:win
pnpm setup:dev
```

Run:
Expand Down
87 changes: 46 additions & 41 deletions docs/INSTALLER.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ This guide explains how to build a distributable installer for **LTX Desktop**.
The installer includes:
- **Electron app** (React frontend + Electron shell)
- **Embedded Python** (version from [`backend/.python-version`](../backend/.python-version)) with all dependencies pre-installed:
- PyTorch (CUDA on Windows, MPS on macOS)
- PyTorch (CUDA on Windows/Linux, MPS on macOS)
- FastAPI, Diffusers, Transformers
- LTX-2 inference packages
- All other required libraries
- **Backend Python code**

**NOT bundled** (downloaded at runtime):
- Model weights (downloaded on first run; can be large) from Hugging Face
- On **Linux** and **Windows**: the Python environment itself is downloaded on first launch (keeps installer small)

The embedded Python is **fully isolated** from the target system's Python — it lives inside `{install_dir}/resources/python/` and never modifies system settings.

Expand All @@ -35,67 +36,43 @@ Before building, ensure you have:

- **Windows**: PowerShell 5.1+ (comes with Windows 10/11)
- **macOS**: Xcode Command Line Tools (`xcode-select --install`)
- **Linux**: `build-essential` (or equivalent) for native extensions

## Quick Build

### macOS
```bash
pnpm build:mac
```

### Windows
```powershell
pnpm build:win
pnpm build
```

This will:
This auto-detects your platform and will:
1. Download a standalone Python distribution (version from [`backend/.python-version`](../backend/.python-version))
2. Install all Python dependencies (~10GB on Windows with CUDA, ~2-3GB on macOS with MPS)
2. Install all Python dependencies (~10GB on Windows/Linux with CUDA, ~2-3GB on macOS with MPS)
3. Build the frontend
4. Package everything with electron-builder
5. Create a DMG (macOS) or NSIS installer (Windows) in the `release/` folder
5. Create a DMG (macOS), AppImage + deb (Linux), or NSIS installer (Windows) in the `release/` folder

## Build Options

### macOS

```bash
# Full build
pnpm build:mac
pnpm build

# Skip Python setup (if already prepared)
pnpm build:mac:skip-python
pnpm build:skip-python

# Fast rebuild (unpacked, skip Python + pnpm install)
pnpm build:fast:mac
pnpm build:fast

# Just prepare Python environment
pnpm prepare:python:mac
pnpm prepare:python
```

### Windows

```powershell
# Full build
pnpm build:win

# Skip Python setup (if already prepared)
pnpm build:win:skip-python

# Just prepare Python environment
pnpm prepare:python:win

# Fast rebuild (unpacked, skip Python + pnpm install)
pnpm build:fast:win

# Clean build
powershell -File scripts/local-build.ps1 -Clean
```
All commands auto-detect the current platform (macOS, Linux, or Windows).

### Build Script Options

The `local-build.sh` script accepts:
- `--platform mac|win` — Target platform (auto-detected if omitted)
The underlying `local-build.sh` / `local-build.ps1` scripts also accept:
- `--platform mac|linux|win` — Target platform (auto-detected if omitted)
- `--skip-python` — Use existing `python-embed/` directory
- `--clean` — Remove build artifacts before starting
- `--unpack` — Build unpacked app only (faster, no installer/DMG)
Expand All @@ -108,6 +85,15 @@ release/
└── LTX Desktop-<version>-arm64.dmg
```

### Linux
```
release/
├── LTX Desktop-x86_64.AppImage
├── LTX Desktop-amd64.deb
├── LTX Desktop-arm64.AppImage
└── LTX Desktop-arm64.deb
```

### Windows
```
release/
Expand All @@ -118,7 +104,7 @@ release/

Place icon files in `resources/` before building:
- `icon.ico` — Windows (multi-size ICO: 256x256, 128x128, 64x64, 48x48, 32x32, 16x16)
- `icon.png` — macOS (1024x1024 recommended)
- `icon.png` — macOS and Linux (1024x1024 recommended)

## Troubleshooting

Expand All @@ -137,6 +123,7 @@ xattr -dr com.apple.quarantine /Applications/LTX\ Desktop.app
### Installer is too large
Expected installer sizes (does not include model weights):
- **Windows**: ~10GB (PyTorch CUDA ~2.5GB + ML libraries ~5GB + Python ~200MB + Electron ~100MB)
- **Linux**: ~10GB (similar to Windows; PyTorch CUDA variant)
- **macOS**: ~2-3GB (PyTorch MPS is much smaller than CUDA variant)

### Runtime / first-run issues
Expand All @@ -156,10 +143,28 @@ pnpm install
pnpm build:frontend

# 4. Build DMG
npx electron-builder --mac
pnpm exec electron-builder --mac

# Or build unpacked app (faster, for testing)
pnpm exec electron-builder --mac --dir
```

### Linux
```bash
# 1. Prepare Python environment
bash scripts/prepare-python.sh

# 2. Install dependencies
pnpm install

# 3. Build frontend
pnpm build:frontend

# 4. Build AppImage + deb
pnpm exec electron-builder --linux

# Or build unpacked app (faster, for testing)
npx electron-builder --mac --dir
pnpm exec electron-builder --linux --dir
```

### Windows
Expand All @@ -174,5 +179,5 @@ pnpm install
pnpm build:frontend

# 4. Build installer
npx electron-builder --win
pnpm exec electron-builder --win
```
1 change: 1 addition & 0 deletions docs/TELEMETRY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ App data folder locations:

- **Windows:** `%LOCALAPPDATA%\LTXDesktop\`
- **macOS:** `~/Library/Application Support/LTXDesktop/`
- **Linux:** `$XDG_DATA_HOME/LTXDesktop/` (default: `~/.local/share/LTXDesktop/`)

Your preference is respected immediately — no restart required.

Expand Down
20 changes: 20 additions & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ dmg:
type: link
path: /Applications

linux:
target:
- AppImage
- deb
icon: resources/icons
category: Video
artifactName: ${productName}-${arch}.${ext}

deb:
depends:
- libgtk-3-0
- libnotify4
- libnss3
- libxss1
- libxtst6
- xdg-utils
- libatspi2.0-0
- libuuid1
- libsecret-1-0

publish:
provider: github
owner: Lightricks
Expand Down
3 changes: 2 additions & 1 deletion electron/app-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ function resolveUserDataPath(): string {
APP_FOLDER_NAME,
)
}
return path.join(os.homedir(), `.${APP_FOLDER_NAME}`)
const xdgData = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share')
return path.join(xdgData, APP_FOLDER_NAME)
}

app.setPath('userData', resolveUserDataPath())
Expand Down
5 changes: 3 additions & 2 deletions electron/export/ffmpeg-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path'
import fs from 'fs'
import { isDev, getCurrentDir } from '../config'
import { logger } from '../logger'
import { getPythonDir } from '../python-setup'

let activeExportProcess: ChildProcess | null = null

Expand All @@ -13,12 +14,12 @@ export function findFfmpegPath(): string | null {
const imageioRelPath = path.join('Lib', 'site-packages', 'imageio_ffmpeg', 'binaries')
binDir = isDev
? path.join(getCurrentDir(), 'backend', '.venv', imageioRelPath)
: path.join(process.resourcesPath, 'python', imageioRelPath)
: path.join(getPythonDir(), imageioRelPath)
} else {
// macOS/Linux: find lib/python3.X/site-packages dynamically
const venvBase = isDev
? path.join(getCurrentDir(), 'backend', '.venv')
: path.join(process.resourcesPath, 'python')
: getPythonDir()
const libDir = path.join(venvBase, 'lib')
if (fs.existsSync(libDir)) {
const pythonDir = fs.readdirSync(libDir).find(e => e.startsWith('python3'))
Expand Down
Loading
Loading