Fail jobs on timeout, treat stale reserved queue rows as orphans, skip migrate/seed on queue/reverb boot, and store how long each successful run took.
286 lines
8.8 KiB
Markdown
286 lines
8.8 KiB
Markdown
# AndyTranscribe
|
|
|
|
Upload pocket-recorder audio (MP3, WAV, OGG, and more), extract embedded metadata, and transcribe locally with [faster-whisper-server](https://github.com/fedirz/faster-whisper-server). Audio never leaves your machine.
|
|
|
|
Built with Laravel 13, Blade, Livewire, Flux UI, Alpine.js, Tailwind CSS 4, [Laravel Reverb](https://laravel.com/docs/reverb), FrankenPHP, and [Laravel AI](https://github.com/laravel/ai).
|
|
|
|
## Features
|
|
|
|
- User accounts with login and open registration (each user only sees their own recordings)
|
|
- Upload common audio formats (MP3, WAV, OGG, FLAC, M4A, AAC, WebM, WMA, AIFF — up to 2 GB)
|
|
- Automatic metadata extraction when tags are present (title, artist, album, duration, recorded date)
|
|
- Search recordings by title, artist, or transcript
|
|
- Queued local transcription (faster-whisper in Docker)
|
|
- Live transcription progress over WebSockets (Reverb) on the list and detail pages
|
|
- Stop or restart a run anytime
|
|
- Copy finished transcripts from the recording detail page
|
|
|
|
## Requirements
|
|
|
|
- [Docker](https://docs.docker.com/get-docker/) and Docker Compose
|
|
- About 2 GB free disk for the Whisper model cache (first run downloads the model)
|
|
|
|
Optional for native PHP development: PHP 8.3+ (8.5 recommended), Composer, Node.js & npm.
|
|
|
|
## Install
|
|
|
|
These steps run the full stack with Docker: web app, queue worker, Reverb WebSockets, and Whisper.
|
|
|
|
### 1. Clone the repository
|
|
|
|
```bash
|
|
git clone <your-repo-url> andyTranscibe
|
|
cd andyTranscibe
|
|
```
|
|
|
|
### 2. Create your environment file
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
### 3. Generate an application key
|
|
|
|
`APP_KEY` is required. Containers refuse to start if it is empty.
|
|
|
|
**Option A — PHP installed on the host**
|
|
|
|
```bash
|
|
php artisan key:generate
|
|
```
|
|
|
|
**Option B — Docker only**
|
|
|
|
Print a key:
|
|
|
|
```bash
|
|
docker run --rm php:8.5-cli php -r "echo 'base64:'.base64_encode(random_bytes(32)), PHP_EOL;"
|
|
```
|
|
|
|
Open `.env` and set:
|
|
|
|
```env
|
|
APP_KEY=base64:paste-the-value-here
|
|
```
|
|
|
|
On Linux you can write it in one step:
|
|
|
|
```bash
|
|
KEY=$(docker run --rm php:8.5-cli php -r "echo 'base64:'.base64_encode(random_bytes(32));")
|
|
sed -i "s|^APP_KEY=.*|APP_KEY=${KEY}|" .env
|
|
```
|
|
|
|
### 4. Start the stack
|
|
|
|
```bash
|
|
docker compose up --build -d
|
|
```
|
|
|
|
Compose builds the **app** image once; `queue` and `reverb` reuse `andytranscribe-app:latest` (no triple rebuild). With the [dev overlay](#local-development-hot-reload), skip `--build` for routine PHP/Blade/JS work — the repo is bind-mounted.
|
|
|
|
On first start the app container will:
|
|
|
|
- create `database/database.sqlite` if needed
|
|
- run migrations
|
|
- seed a demo user (see below)
|
|
- start FrankenPHP on port **8080**
|
|
|
|
Whisper may take a minute or two while the model downloads.
|
|
|
|
### Demo login
|
|
|
|
Every container start runs `db:seed`, which ensures these users exist:
|
|
|
|
| Email | Password |
|
|
| --- | --- |
|
|
| `demo@example.com` | `password` |
|
|
| `admin@example.com` | `password` |
|
|
|
|
Override the demo user with `SEED_USER_NAME`, `SEED_USER_EMAIL`, and `SEED_USER_PASSWORD` in `.env`.
|
|
|
|
### 5. Open the app
|
|
|
|
| URL | Purpose |
|
|
| --- | --- |
|
|
| [http://localhost:8080/recordings](http://localhost:8080/recordings) | App UI |
|
|
| [http://localhost:8080/up](http://localhost:8080/up) | Health check |
|
|
|
|
Live transcription status uses WebSockets on port **8081** (Reverb). Keep that port reachable from your browser.
|
|
|
|
### 6. Verify services (optional)
|
|
|
|
```bash
|
|
docker compose ps
|
|
docker compose logs -f app queue whisper reverb
|
|
```
|
|
|
|
You should see `app`, `queue`, `reverb`, and `whisper` running. Whisper becomes healthy after `/health` succeeds.
|
|
|
|
### Stop / restart
|
|
|
|
```bash
|
|
docker compose down
|
|
docker compose up -d
|
|
```
|
|
|
|
Data under `./database`, `./storage/app`, and `./storage/logs` is kept on the host.
|
|
|
|
If `docker compose build` fails with `can't stat .../storage/app/private/livewire-tmp`, a container created that directory as root. Fix ownership (or remove it), then rebuild:
|
|
|
|
```bash
|
|
sudo chown -R "$USER:$USER" storage
|
|
# or: sudo rm -rf storage/app/private/livewire-tmp
|
|
docker compose up --build -d
|
|
```
|
|
|
|
### Live reload while developing
|
|
|
|
Default Compose uses the built image, so PHP/Blade/CSS/JS changes need a rebuild. For day-to-day work, use the dev overlay (bind-mounts the repo and runs Vite HMR):
|
|
|
|
```bash
|
|
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build -d
|
|
```
|
|
|
|
Or set once in `.env`:
|
|
|
|
```env
|
|
COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml
|
|
```
|
|
|
|
Then a normal `docker compose up -d` enables:
|
|
|
|
- host source mounted at `/app` (PHP, Blade, routes, etc. without rebuild)
|
|
- `vite` on port **5173** for CSS/JS hot reload and Blade refresh
|
|
- `queue:listen` so worker code picks up changes between jobs
|
|
|
|
Open [http://localhost:8080](http://localhost:8080) as usual. After changing Composer packages, run `docker compose exec app composer install`.
|
|
|
|
## CI/CD (Gitea Actions)
|
|
|
|
On push to `main`, Gitea Actions (host runner on z00):
|
|
|
|
1. Runs PHPUnit (+ compose config check)
|
|
2. Builds and pushes `gitea.z00.nu/ben/andytranscribe:<sha>` (+ `:latest`) — Vite assets are baked in the image build
|
|
3. Deploys by hard-resetting `~/andyTranscibe` to that SHA and pulling the image (`docker-compose.yml` + `compose.z00.yaml`)
|
|
|
|
Do not hot-patch production containers or the deploy checkout. Fix in git and push to `main` so CI deploys.
|
|
|
|
One-time server bootstrap (secrets + registry login):
|
|
|
|
```bash
|
|
./scripts/setup-gitea-ci.sh
|
|
```
|
|
|
|
Manual deploy of an already-built tag:
|
|
|
|
```bash
|
|
APP_IMAGE=gitea.z00.nu/ben/andytranscribe:<sha> DEPLOY_PATHS=$HOME/andyTranscibe ./scripts/deploy-production.sh
|
|
```
|
|
|
|
## Services and ports
|
|
|
|
| Service | Host port | Role |
|
|
| --- | --- | --- |
|
|
| `app` | `8080` | FrankenPHP (Laravel web UI) |
|
|
| `reverb` | `8081` | WebSockets for live transcription status |
|
|
| `whisper` | `8090` | faster-whisper HTTP API |
|
|
| `queue` | — | `php artisan queue:work` for transcription jobs |
|
|
| `vite` | `5173` | Vite HMR (dev overlay only) |
|
|
|
|
### Persistent data
|
|
|
|
| Host path | Container path | Contents |
|
|
| --- | --- | --- |
|
|
| `./database` | `/app/database` | SQLite database |
|
|
| `./storage/app` | `/app/storage/app` | Uploaded audio (`private/recordings`) |
|
|
| `./storage/logs` | `/app/storage/logs` | Application logs |
|
|
|
|
Whisper model cache uses the Docker volume `whisper-huggingface-cache`.
|
|
|
|
### Useful environment variables
|
|
|
|
Edit `.env` before `docker compose up` when you need different ports or models:
|
|
|
|
| Variable | Purpose | Default |
|
|
| --- | --- | --- |
|
|
| `APP_KEY` | Required Laravel encryption key | — |
|
|
| `APP_IMAGE` | Pre-built image for CI/prod deploys (omit locally) | `andytranscribe-app:latest` |
|
|
| `APP_URL` | Public app URL | `http://localhost:8080` |
|
|
| `APP_HOST_PORT` | Host port for the web app | `8080` |
|
|
| `REVERB_HOST_PORT` | Host port for WebSockets | `8081` |
|
|
| `WHISPER_HOST_PORT` | Host port for Whisper | `8090` |
|
|
| `LOCAL_WHISPER_MODEL` | Whisper model id | `Systran/faster-whisper-base` |
|
|
| `TRANSCRIPTION_TIMEOUT` | Job/HTTP timeout (seconds). Hung Whisper calls fail the job; UI can restart. | `600` |
|
|
| `DB_QUEUE_RETRY_AFTER` | Must exceed `TRANSCRIPTION_TIMEOUT` | `660` |
|
|
|
|
Inside Compose, Laravel talks to Whisper at `http://whisper:8000/v1` and publishes broadcasts to the `reverb` service. The browser connects to Reverb on `localhost:8081`.
|
|
|
|
If you change `REVERB_APP_KEY` or browser-facing Reverb host/port settings, rebuild so Vite embeds the new values:
|
|
|
|
```bash
|
|
docker compose up --build -d
|
|
```
|
|
|
|
### GPU Whisper (optional)
|
|
|
|
```bash
|
|
docker compose --profile gpu up -d --build
|
|
```
|
|
|
|
Use the GPU Whisper service instead of the CPU `whisper` service when you have an NVIDIA GPU and the NVIDIA Container Toolkit installed.
|
|
|
|
## Usage
|
|
|
|
1. Open the app and **Log in** with `admin@example.com` / `password` (or `demo@example.com` / `password`), or **Register** a new account.
|
|
2. Open **Recordings → Upload** and drop one or many audio files.
|
|
3. Transcription starts automatically (the `queue` service must be running).
|
|
4. Watch live progress on the list or detail page; stop or restart anytime.
|
|
5. Search by title, artist, or transcript text.
|
|
6. For older uploads still **Queued** with no progress, use **Queue pending transcriptions** on the recordings list.
|
|
|
|
The demo user is re-seeded on every container start. Any recordings with no owner are assigned to that demo user. Later registered users only see their own uploads.
|
|
|
|
Finished transcripts are stored on each recording and are included in search.
|
|
|
|
## Native PHP development (optional)
|
|
|
|
For hacking on the Laravel app outside the FrankenPHP image:
|
|
|
|
```bash
|
|
composer setup
|
|
docker compose up -d whisper reverb
|
|
```
|
|
|
|
Then in separate terminals:
|
|
|
|
```bash
|
|
php artisan serve --port=8000
|
|
php artisan queue:work
|
|
php artisan reverb:start
|
|
npm run dev
|
|
```
|
|
|
|
Point `.env` at local services, for example:
|
|
|
|
```env
|
|
APP_URL=http://localhost:8000
|
|
LOCAL_WHISPER_URL=http://127.0.0.1:8090/v1
|
|
REVERB_HOST=localhost
|
|
REVERB_PORT=8080
|
|
VITE_REVERB_HOST=localhost
|
|
VITE_REVERB_PORT=8080
|
|
VITE_REVERB_SCHEME=http
|
|
```
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
composer test
|
|
# or
|
|
php artisan test
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|