Skip to content
Open
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
90 changes: 90 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Build LTX-WS Videofentanyl macOS .app with PyInstaller and attach to GitHub Releases.
#
# Normal path: publish a GitHub Release → builds that tag and attaches the zip.
# Manual test: Actions → Release → Run workflow
# - ref: branch/SHA to build (e.g. PyInstaller) before merge
# - release_tag: label for the zip filename (e.g. ci-test, v0.2.0-rc1)
# - attach_to_release: optional upload to an existing release with that tag

name: Release

on:
release:
types: [published]
workflow_dispatch:
inputs:
ref:
description: "Git ref to build (branch, tag, or SHA). Empty = branch selected below."
required: false
default: ""
release_tag:
description: "Version label for the zip filename (ignored when triggered by publishing a release)."
required: true
default: ci-test
attach_to_release:
description: "Attach zip to an existing GitHub Release named release_tag (manual runs only)."
type: boolean
required: false
default: false

permissions:
contents: write

concurrency:
group: release-${{ github.event.release.tag_name || inputs.release_tag || github.run_id }}
cancel-in-progress: true

jobs:
build-macos-app:
name: macOS app (arm64)
runs-on: macos-14
timeout-minutes: 90
env:
RELEASE_VERSION: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}
CHECKOUT_REF: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.ref != '' && inputs.ref || github.ref }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ env.CHECKOUT_REF }}
fetch-depth: 0

- name: Build context
run: |
echo "event=${{ github.event_name }}"
echo "checkout_ref=${CHECKOUT_REF}"
echo "release_version=${RELEASE_VERSION}"

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: web/package-lock.json

- name: Install Python build dependencies
run: |
chmod +x scripts/ci_install_build_deps.sh scripts/build_mac_app.sh
./scripts/ci_install_build_deps.sh

- name: Build macOS app
run: ./scripts/build_mac_app.sh --zip

- name: List build output
run: ls -la dist/

- name: Upload workflow artifact
uses: actions/upload-artifact@v4
with:
name: LTX-WS-Videofentanyl-${{ env.RELEASE_VERSION }}-macos-arm64
path: dist/LTX-WS-Videofentanyl-*-macos-arm64.zip
if-no-files-found: error

- name: Attach to GitHub Release
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.attach_to_release)
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.release.tag_name || inputs.release_tag }}
files: dist/LTX-WS-Videofentanyl-*-macos-arm64.zip
fail_on_unmatched_files: true
overwrite_files: true
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,21 @@ Build once (or after editing `web/`):
cd web && npm install && npm run build && cd ..
```

### macOS app (PyInstaller)

Build a double-clickable **LTX-WS Videofentanyl** `.app` (no terminal; status in the Web UI header). See [docs/PACKAGING.md](docs/PACKAGING.md).

**Download:** published GitHub Releases include `LTX-WS-Videofentanyl-<tag>-macos-arm64.zip` (built by [.github/workflows/release.yml](.github/workflows/release.yml)).

**Build locally:**

```bash
./scripts/ci_install_build_deps.sh
./scripts/build_mac_app.sh
```

Output: `dist/LTX-WS Videofentanyl.app`. Models and outputs live under `~/Library/Application Support/LTX-WS/`.

### Hugging Face auth

For gated or private Hub repos: set [`HF_TOKEN`](https://huggingface.co/docs/huggingface_hub/package_reference/environment_variables) or run `huggingface-cli login`.
Expand Down
82 changes: 82 additions & 0 deletions app_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
macOS app entry point for PyInstaller builds.

Configures writable Application Support paths, file logging, and opens the
embedded Web UI in the default browser.
"""

from __future__ import annotations

import argparse
import logging
import sys
import threading
import time
import webbrowser


def _setup_logging() -> None:
from ltx_paths import configure_frozen_environment, is_frozen, logs_dir

configure_frozen_environment()
handlers: list[logging.Handler] = [logging.StreamHandler(sys.stderr)]
if is_frozen():
log_file = logs_dir() / "ltx-ws.log"
handlers.append(logging.FileHandler(log_file, encoding="utf-8"))
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(message)s",
datefmt="%H:%M:%S",
handlers=handlers,
)


def main() -> None:
from ltx_paths import is_frozen

_setup_logging()
if is_frozen():
from system_status import set_status

set_status("idle", "Starting…")
parser = argparse.ArgumentParser(description="LTX-WS Videofentanyl")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8765)
parser.add_argument(
"--open-browser",
action="store_true",
default=is_frozen(),
help="open Web UI in browser (default when frozen)",
)
parser.add_argument("--model", default="auto")
args, _unknown = parser.parse_known_args()

if args.open_browser:
url = f"http://{args.host}:{args.port}/"
threading.Thread(
target=lambda: (time.sleep(2.0), webbrowser.open(url)),
daemon=True,
).start()

# Delegate to server.main with equivalent CLI flags.
argv = [
"server.py",
"--web-ui",
"--host",
args.host,
"--port",
str(args.port),
"--model",
args.model,
]
if args.open_browser:
argv.append("--open-browser")
sys.argv = [sys.argv[0]] + argv

from server import main as server_main

server_main()


if __name__ == "__main__":
main()
87 changes: 87 additions & 0 deletions docs/PACKAGING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Packaging: macOS app (PyInstaller)

Build a double-clickable **LTX-WS Videofentanyl** app for Apple Silicon.

## Prerequisites

- macOS on Apple Silicon
- Python 3.11+ venv with all runtime deps (`requirements.txt` + ltx-2-mlx v0.14.9)
- Node.js 18+ (`cd web && npm install`)
- PyInstaller: `pip install pyinstaller`

## Build (local)

```bash
chmod +x scripts/build_mac_app.sh scripts/ci_install_build_deps.sh
./scripts/ci_install_build_deps.sh
./scripts/build_mac_app.sh
```

Output: `dist/LTX-WS Videofentanyl.app`

Zip for distribution:

```bash
./scripts/build_mac_app.sh --zip
# dist/LTX-WS-Videofentanyl-<version>-macos-arm64.zip
```

## GitHub Releases (CI)

Publishing a GitHub Release runs [.github/workflows/release.yml](../.github/workflows/release.yml):

1. Builds the Web UI (`npm ci` + `vite build`)
2. Installs Python deps via `scripts/ci_install_build_deps.sh`
3. Runs PyInstaller on `macos-14` (Apple Silicon)
4. Zips the `.app` and attaches `LTX-WS-Videofentanyl-<tag>-macos-arm64.zip` to the release

**To ship a build:** create a release on GitHub (tag + publish). No local build required.

Manual test without publishing: Actions → **Release** → **Run workflow**

| Input | Purpose |
|-------|---------|
| **ref** | Branch or SHA to build (e.g. `PyInstaller`). Empty = branch you pick in the UI. |
| **release_tag** | Zip filename label (e.g. `ci-test`, `v0.2.0-rc1`). |
| **attach_to_release** | Upload to an existing GitHub Release with that tag (optional). |

Produces a workflow artifact; release attachment only when publishing a release or when `attach_to_release` is enabled.

First launch opens `http://127.0.0.1:8765/` in your browser. The app runs headless (no terminal).

## Runtime paths (frozen)

| Data | Location |
|------|----------|
| Models | `~/Library/Application Support/LTX-WS/models/` |
| LoRAs | `~/Library/Application Support/LTX-WS/loras/` |
| Web outputs | `~/Library/Application Support/LTX-WS/web_outputs/` |
| Logs | `~/Library/Application Support/LTX-WS/logs/ltx-ws.log` |

Override base: `LTX_WS_DATA_DIR=/path`

## UI status indicators

Without a console, startup progress is shown in the Web UI header:

- Model download (Hugging Face snapshot progress)
- MLX / pipeline loading
- LoRA download
- Active model when ready

Subscribe API: `GET /api/system/events` (SSE), snapshot: `GET /api/system/status`

## Dev entry (non-frozen)

```bash
python app_main.py --open-browser --model auto
```

Equivalent to `python server.py --web-ui --open-browser`.

## Notes

- MLX weights are **not** bundled; first run downloads to Application Support.
- `ffmpeg` is not bundled; autoconcat requires `ffmpeg` on PATH.
- PyInstaller + MLX is fragile across versions; test on a clean machine after building.
- For CLI/MCP, continue using `python server.py` / `videofentanyl.py` from a venv.
Loading