# 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 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:` (+ `: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: 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