ben 1877fee258
CI / test (push) Successful in 20s
CI / build-and-push (push) Successful in 38s
CI / deploy (push) Successful in 9s
Fix flaky transcription duration assertion in CI.
Use integer timestamps for duration and pin the test clock to a whole second so SQLite round-trips stay stable.
2026-08-13 00:49:53 +02:00
2026-08-12 13:59:33 +02:00
2026-08-12 13:59:33 +02:00
2026-08-12 13:59:33 +02:00
2026-08-12 13:59:33 +02:00

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.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 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)
  • 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 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):

./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). 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:

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

  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:

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

S
Description
Upload pocket-recorder MP3s and transcribe them
Readme
511 KiB
Languages
PHP 59.3%
Blade 34.9%
JavaScript 2.5%
Shell 2.3%
Dockerfile 0.9%
Other 0.1%