Feature tests were failing on the host runner with ViteManifestNotFoundException after the flaky Node build step was removed.
AndyTranscribe
Upload pocket-recorder audio (MP3, WAV, OGG, and more), extract embedded metadata, and transcribe locally with faster-whisper-server. Audio never leaves your machine.
Built with Laravel 13, Blade, Livewire, Flux UI, Alpine.js, Tailwind CSS 4, Laravel Reverb, FrankenPHP, and 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 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
git clone <your-repo-url> andyTranscibe
cd andyTranscibe
2. Create your environment file
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
php artisan key:generate
Option B — Docker only
Print a key:
docker run --rm php:8.5-cli php -r "echo 'base64:'.base64_encode(random_bytes(32)), PHP_EOL;"
Open .env and set:
APP_KEY=base64:paste-the-value-here
On Linux you can write it in one step:
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
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, skip --build for routine PHP/Blade/JS work — the repo is bind-mounted.
On first start the app container will:
- create
database/database.sqliteif 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:
| 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 | App UI |
| 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)
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
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:
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):
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build -d
Or set once in .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) viteon port 5173 for CSS/JS hot reload and Blade refreshqueue:listenso worker code picks up changes between jobs
Open 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):
- Runs PHPUnit (+ compose config check)
- Builds and pushes
gitea.z00.nu/ben/andytranscribe:<sha>(+:latest) — Vite assets are baked in the image build - Deploys by hard-resetting
~/andyTranscibeto 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):
./scripts/setup-gitea-ci.sh
Manual deploy of an already-built tag:
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) | 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:
docker compose up --build -d
GPU Whisper (optional)
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
- Open the app and Log in with
admin@example.com/password(ordemo@example.com/password), or Register a new account. - Open Recordings → Upload and drop one or many audio files.
- Transcription starts automatically (the
queueservice must be running). - Watch live progress on the list or detail page; stop or restart anytime.
- Search by title, artist, or transcript text.
- 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:
composer setup
docker compose up -d whisper reverb
Then in separate terminals:
php artisan serve --port=8000
php artisan queue:work
php artisan reverb:start
npm run dev
Point .env at local services, for example:
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
composer test
# or
php artisan test
License
MIT