Skip to content
Draft
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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ recursive-include mlx_audio/tts *.tsx
recursive-include mlx_audio/tts *.mjs
recursive-include mlx_audio/tts *.css
recursive-include mlx_audio/tts *.md
recursive-include mlx_audio/ui *

# Exclude large directories
prune mlx_audio/ui/node_modules
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ The best audio processing library built on Apple's MLX framework, providing fast
- Quantization support (3-bit, 4-bit, 6-bit, 8-bit, and more) for optimized performance
- Swift package for iOS/macOS integration

## Quickstart (macOS, Apple Silicon)

If you want the web UI + API server in one command:

```bash
# Install prerequisites (Homebrew must be /opt/homebrew on Apple Silicon)
brew install ffmpeg uv

# Run everything (API server + web UI)
uvx --from "mlx-audio[app]" mlx_audio.dev
```

**Homebrew note:** on Apple Silicon, `brew --prefix` should return `/opt/homebrew`
(not the Intel `/usr/local`). If it doesn’t, fix Homebrew first or you may hit
missing dependencies.

If you cloned the repo, you can also double‑click `mlx_audio_dev.command`.

## Installation

### Using pip
Expand Down Expand Up @@ -335,6 +353,12 @@ MLX-Audio includes a modern web interface and OpenAI-compatible API.
### Starting the Server

```bash
# One-command dev server + UI
mlx_audio.dev

# Or run via uvx (installs server + STS deps)
uvx --from "mlx-audio[app]" mlx_audio.dev

# Start API server
mlx_audio.server --host 0.0.0.0 --port 8000

Expand Down
160 changes: 160 additions & 0 deletions mlx_audio/dev.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"""Launch MLX-Audio API server and web UI dev server."""

from __future__ import annotations

import argparse
import os
import shutil
import signal
import subprocess
import sys
import time
from pathlib import Path
from typing import Iterable, Optional, Sequence


def _iter_ui_candidates(ui_dir: Optional[str]) -> Iterable[Path]:
if ui_dir:
yield Path(ui_dir)

env_dir = os.getenv("MLX_AUDIO_UI_DIR")
if env_dir:
yield Path(env_dir)

cwd = Path.cwd()
yield cwd / "mlx_audio" / "ui"
yield cwd / "ui"

here = Path(__file__).resolve().parent
yield here / "ui"
yield here.parent / "mlx_audio" / "ui"


def _resolve_ui_dir(ui_dir: Optional[str]) -> Path:
seen = set()
for candidate in _iter_ui_candidates(ui_dir):
if not candidate:
continue
if candidate in seen:
continue
seen.add(candidate)
if (candidate / "package.json").exists():
return candidate

raise FileNotFoundError(
"Could not find the UI directory. Set MLX_AUDIO_UI_DIR or pass --ui-dir."
)


def _require_npm() -> str:
npm = shutil.which("npm")
if npm is None:
raise RuntimeError("npm is required to run the web UI. Install Node.js first.")
return npm


def _spawn(cmd: Sequence[str], cwd: Optional[Path] = None) -> subprocess.Popen:
env = os.environ.copy()
env.setdefault("PYTHONUNBUFFERED", "1")
return subprocess.Popen(
cmd,
cwd=str(cwd) if cwd else None,
env=env,
start_new_session=True,
)


def _terminate(proc: subprocess.Popen, name: str) -> None:
if proc.poll() is not None:
return

try:
if os.name != "nt":
os.killpg(proc.pid, signal.SIGTERM)
else:
proc.terminate()
except ProcessLookupError:
return

try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
if os.name != "nt":
os.killpg(proc.pid, signal.SIGKILL)
else:
proc.kill()


def main() -> int:
parser = argparse.ArgumentParser(
description="Run MLX-Audio API server and web UI dev server together."
)
parser.add_argument("--host", default="0.0.0.0", help="Server host")
parser.add_argument("--port", type=int, default=8000, help="Server port")
parser.add_argument(
"--ui-dir",
default=None,
help="Path to the UI directory (defaults to mlx_audio/ui)",
)
parser.add_argument(
"--skip-npm-install",
action="store_true",
help="Skip running npm install",
)

args = parser.parse_args()

ui_dir = _resolve_ui_dir(args.ui_dir)
npm = _require_npm()

server_cmd = [
sys.executable,
"-m",
"mlx_audio.server",
"--host",
args.host,
"--port",
str(args.port),
]

server_proc = _spawn(server_cmd)

try:
if not args.skip_npm_install:
subprocess.run([npm, "install"], cwd=ui_dir, check=True)
ui_proc = _spawn([npm, "run", "dev"], cwd=ui_dir)
except Exception:
_terminate(server_proc, "server")
raise

print(
f"Server running on http://{args.host}:{args.port} (API). "
"UI dev server starting on http://localhost:3000"
)
print("Press Ctrl+C to stop.")

exit_code = 0
try:
while True:
server_ret = server_proc.poll()
ui_ret = ui_proc.poll()
if server_ret is not None:
print(f"Server exited with code {server_ret}.")
exit_code = server_ret or 1
break
if ui_ret is not None:
print(f"UI dev server exited with code {ui_ret}.")
exit_code = ui_ret or 1
break
time.sleep(0.5)
except KeyboardInterrupt:
exit_code = 130
finally:
_terminate(ui_proc, "ui")
_terminate(server_proc, "server")

return exit_code


if __name__ == "__main__":
raise SystemExit(main())
30 changes: 30 additions & 0 deletions mlx_audio_dev.command
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
set -euo pipefail

if [[ "$(uname -s)" != "Darwin" ]]; then
echo "This launcher is intended for macOS."
read -r -p "Press Enter to close..." _
exit 1
fi

if ! command -v uvx >/dev/null 2>&1; then
echo "uvx is not installed. Install uv first: brew install uv"
read -r -p "Press Enter to close..." _
exit 1
fi

if command -v brew >/dev/null 2>&1; then
brew_prefix=$(brew --prefix 2>/dev/null || true)
if [[ -n "$brew_prefix" && "$brew_prefix" != "/opt/homebrew" ]]; then
echo "Warning: Homebrew prefix is '$brew_prefix'."
echo "On Apple Silicon, use Homebrew at /opt/homebrew (not the Intel /usr/local)."
echo "This may cause missing deps if you are on Apple Silicon."
echo
fi
fi

echo "Starting MLX-Audio API server and UI dev server..."
uvx --from "mlx-audio[app]" mlx_audio.dev

echo
read -r -p "Press Enter to close..." _
24 changes: 24 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,28 @@ all = [
"webrtcvad>=2.0.10",
]

# App/dev server dependencies (API + STS + dev helpers)
app = [
"tiktoken>=0.9.0",
"mistral-common[audio]",

"misaki>=0.9.4",
"num2words>=0.5.14",
"spacy>=3.8.4",
"phonemizer-fork>=3.3.2",
"espeakng-loader>=0.2.4",
"sentencepiece>=0.2.0",

"fastapi>=0.95.0",
"uvicorn>=0.22.0",
"python-multipart>=0.0.22",

"webrtcvad>=2.0.10",

"pytest>=7.0.0",
"pytest-asyncio>=1.0.0",
]

# Development dependencies
dev = [
"pytest>=7.0.0",
Expand All @@ -103,6 +125,7 @@ dev = [
"mlx_audio.stt.generate" = "mlx_audio.stt.generate:main"
"mlx_audio.tts.generate" = "mlx_audio.tts.generate:main"
"mlx_audio.server" = "mlx_audio.server:main"
"mlx_audio.dev" = "mlx_audio.dev:main"

[project.urls]
Homepage = "https://github.com/Blaizzy/mlx-audio"
Expand All @@ -128,6 +151,7 @@ mlx_audio = [
"tts/*.css",
"tts/**/*.json",
"tts/static/**/*",
"ui/**/*",
]

[tool.black]
Expand Down
Loading