A seamless Chinese worship music transition system designed to analyze songs (tempo, key, structure) and generate smooth transitions between them.
End Goals:
- Generate audio files containing multiple songs with smooth transitions between songs
- Generate video files containing lyrics videos of multiple songs with smooth transitions
- Provide an interactive tool to select songs from the library, experiment with transition parameters, and generate output audio/video files
- Provide an admin tool to manage the song library (via scraping sop.org) and perform song analysis and lyrics LRC generation
Note: This repository contains both lightweight tools such as sow-admin and heavier delivery/analysis services. They are architecturally separate but co-located in a monorepo.
Worship leaders: see USER_GUIDE.md for preparing a worship set for offline playback and starting it without Wi-Fi.
This project consists of several architecturally separate components. Here's how to run each primary component:
| Component | Purpose | Run Command |
|---|---|---|
| Admin CLI | Catalog management, audio download | uv run --project ops/admin-cli --extra admin sow-admin --help |
| User App | Interactive TUI for transitions | uv run --project lab/sow-app sow-app run |
| Web App | Browser-based worship set editor (login-gated) | pnpm --filter sow-webapp dev |
| Marketing Site | Public marketing pages (landing, about, docs) | pnpm --filter sow-marketing dev |
| Android App | Native mobile worship set editor/player | cd delivery/android && ./gradlew assembleDebug |
| Analysis Service | Audio analysis & stem separation | cd ops/analysis-service && docker compose up -d |
| Render Worker | Serverless render processing | cd delivery/render-worker && docker compose up --build |
- Admin CLI & User App: Python 3.11+,
uvpackage manager - Web App: Node.js 18+,
pnpmpackage manager - Android App: Android Studio/JDK 17, Android SDK 35, Android 8.0+ device or emulator
- Web App Database: PostgreSQL with the
pgvectorextension available - Analysis Service: Docker Desktop, Cloudflare R2 credentials
# Initialize database
uv run --project ops/admin-cli --extra admin sow-admin db init
# Scrape song catalog
uv run --project ops/admin-cli --extra admin sow-admin catalog scrape
# Download audio
uv run --project ops/admin-cli --extra admin sow-admin audio download --song-id "song-id"# Run the interactive TUI
uv run --project lab/sow-app sow-app run
# Or with custom config
uv run --project lab/sow-app sow-app run --config /path/to/config.tomlcd ops/analysis-service
# Copy and configure environment
cp .env.example .env
# Edit .env with R2 credentials
# Build and start
docker compose build # First time only (10-20 min)
docker compose up -d
# Check health
curl http://localhost:8000/api/v1/health# Install dependencies (from project root)
pnpm install
# Start dev server
pnpm --filter sow-webapp dev
# Or from the webapp directory
cd delivery/webapp && pnpm devcd delivery/android
# Run JVM tests and coverage
./gradlew testDebugUnitTest koverXmlReport
# Build debug APK
./gradlew assembleDebugThe debug API base URL defaults to http://10.0.2.2:8080 for an Android
emulator pointed at the local webapp. See
delivery/android/README.md for physical-device
networking, Better Auth cookie/origin troubleshooting, signed URL playback, and
release build notes.
The public marketing pages (landing, about, docs) moved to the separate Marketing Site component (delivery/marketing, served at https://streamofworship.com). The webapp at https://app.streamofworship.com is login-first — unauthenticated visits to any app path redirect to /login — and in-app About/Docs links point at the marketing domain via NEXT_PUBLIC_MARKETING_URL (issue #213).
The Web App is the primary end-user interface for worship leaders and media teams. It is a Next.js browser application offering phone-first worship set preparation and playback, with desktop power-mode for advanced editing.
Songset Management
- Browse and search the master song catalog (title, artist, key, tempo)
- Create multi-song worship sets with per-song transition configuration
- Adjust gap duration and crossfade for each song pair
- Desktop: key shift and tempo nudge per song
Render Pipeline
- Generate MP3 audio: blended multi-song mix with smooth transitions (processed asynchronously via AWS Lambda)
- Generate MP4 video: synchronized lyrics video with chapter markers (processed asynchronously via AWS Lambda)
- Real-time progress via SSE (Server-Sent Events)
- Configurable video resolution (720p, 1080p, 1440p) and font size
Playback
- Built-in controller player (audio + synchronized lyrics)
- Second-screen projection via W3C Presentation API or Google Cast
- Offline caching of rendered files via Service Worker (see Offline Playback; developer walkthrough: Offline Worship Design)
- Full keyboard shortcuts and Media Session API integration
Content Review
- LRC lyrics review and editing (fix timing, correct characters)
- Transition detail sheet (waveform alignment preview)
- Semantic song search: full-text tsvector across Chinese text and pinyin, plus similar-song discovery via pre-computed embeddings
Sharing & Settings
- Generate shareable public player links with configurable expiry
- User settings (display name, default transition parameters)
- Node.js 18+
- pnpm
External services (required for full functionality):
- Neon PostgreSQL database
- pgvector enabled in that PostgreSQL database
- Cloudflare R2 object storage (for rendered audio/video files)
- AWS SQS queue + Lambda (for render job processing)
# Install dependencies
cd delivery/webapp && pnpm install
# Configure environment
cp .env.example .env.local
# Edit .env.local with your credentials (see .env.production.example for full docs)
# Push database schema
psql "$DATABASE_URL" -c 'CREATE EXTENSION IF NOT EXISTS vector;'
npx drizzle-kit push
# Start dev server
pnpm dev # → http://localhost:8080See delivery/webapp/.env.production.example for documentation of all environment variables.
Note on LLM vs. embedding env vars: The Web App does not use the
SOW_LLM_API_KEY/SOW_LLM_MODEL/SOW_LLM_BASE_URLvariables. Those belong to the Python Analysis Service (chat for LRC/transcript workers and agentic songset construction). The Web App's only LLM-adjacent feature is semantic song search, which uses the separateSOW_EMBEDDING_API_KEY/SOW_EMBEDDING_BASE_URL/SOW_EMBEDDING_MODELvariables. If you seeSOW_LLM_*set on Vercel for the webapp, it is currently unused/dormant there.
pnpm dev # Dev server on :8080
pnpm test # Run test suite (Vitest)
pnpm lint # ESLint check
pnpm build # Production build
# Database migrations
npx drizzle-kit push # Push schema to DB (dev)
npx drizzle-kit generate # Generate migration files
npx drizzle-kit migrate # Run pending migrations| Path | Description |
|---|---|
/login |
User login |
/register |
User registration |
/songsets |
Songset list |
/songsets/[id] |
Songset editor |
/songsets/[id]/render |
Render configuration and progress |
/songsets/[id]/play |
Playback controller |
/songsets/[id]/play/controller |
Controller player (Presentation API) |
/songsets/[id]/play/projection |
Second-screen lyrics projection |
/share/[token] |
Public shared player |
/share/[token]/play/audio |
Shared audio playback |
/share/[token]/play/projection |
Shared projection playback |
/settings |
User settings |
The old public
/,/about, and/docsroutes were removed (issue #213); they now live on the marketing site (delivery/marketing,https://streamofworship.com).
GET /api/songs,GET /api/songs/[id],GET /api/songs/search,GET /api/songs/albums,POST /api/songs/search/semantic: authenticated catalog APIs. Semantic search requires arecordingIdand uses pre-computed embeddings from the database. App users only see songs with at least one published recording.GET|POST|PATCH|DELETE /api/songsets...: authenticated songset CRUD, item editing, and reorder operations scoped to the owner.POST /api/render-jobs,GET /api/render-jobs/[id],DELETE /api/render-jobs/[id],GET /api/render-jobs/[id]/events,GET /api/render-jobs/[id]/artifact-sizes: authenticated render creation, polling, cancellation, SSE progress, and artifact size queries.GET|POST /api/signed-url: authenticated signed URL minting for published recordings byhashPrefixor the caller's own render job artifacts byrenderJobId.POST /api/transitions/preview: authenticated transition audio preview signed URL generation.GET|DELETE /api/offline/cache: authenticated offline artifact URL generation and invalidation.GET|PUT /api/settings: authenticated per-user settings.GET|POST|DELETE /api/lyrics/marks,GET|PUT|DELETE /api/lyrics/overrides: authenticated lyric review and override storage.POST /api/share,GET /api/share,DELETE /api/share/[token]: authenticated share management with a 20-active-share cap per user.GET /api/share/[token]: public share-token lookup used by shared playback pages.
The web app deploys to Vercel. Render jobs are enqueued to AWS SQS and processed by a Lambda worker (Docker container deployed to private ECR), so the Vercel function only needs a short timeout for job creation and SSE progress streaming.
See delivery/webapp/README.md for full deployment instructions including:
- Vercel project setup
- Environment variable configuration
- Google Cast SDK receiver registration (dev / staging / production)
- Cast production approval process
The public marketing surface: landing, about, and docs pages, bilingual (English at root, 繁體中文 under /zh-Hant, switched via plain links). Tech: Next.js 16 static export (output: "export"), Tailwind v4.
pnpm --filter sow-marketing dev # dev server on :3001
pnpm --filter sow-marketing build # static export to delivery/marketing/out/
pnpm --filter sow-marketing lint
pnpm --filter sow-marketing typecheckEnvironment: NEXT_PUBLIC_APP_URL (default https://app.streamofworship.com) points the register/login CTAs at the webapp. No database, no auth, no secrets.
Deployment: marketing owns https://streamofworship.com serving delivery/marketing/out/; the webapp owns https://app.streamofworship.com. Infra provisioning is not yet done (see issue #213 deployment notes). See delivery/marketing/README.md.
The Admin CLI is the backend management tool for administrators and DevOps. It manages the song catalog, downloads audio, and coordinates with the Analysis Service.
Database Management
- Initialize PostgreSQL database with schema creation
- Database status and health checks
Catalog Management
- Scrape song catalog from sop.org (Stream of Praise)
- Search songs by title, artist, album, or lyrics
- List albums and songs with filtering
- Incremental updates (only new songs)
Audio Management
- Download audio from YouTube via yt-dlp
- Content-hash based deduplication (SHA-256)
- Upload to Cloudflare R2 storage
- List recordings with status filtering
Analysis Coordination
- Submit audio analysis jobs to Analysis Service
- Check analysis job status
- Monitor stem separation progress
Songset Management
- Create and manage songsets
- Export songsets to JSON
- Backup and restore songset databases
# Database operations
sow-admin db init # Initialize database
sow-admin db status # Check database status
# Catalog operations
sow-admin catalog scrape # Scrape all songs from sop.org
sow-admin catalog scrape --incremental # Only new songs
sow-admin catalog search "主祢是愛" # Search by title/lyrics
sow-admin catalog list --album "敬拜讚美15" # List songs in album
sow-admin catalog show <song-id> # Show song details
# Audio operations
sow-admin audio download --song-id "zhu-ni-shi-ai-1" # Download specific song
sow-admin audio list # List all recordings
sow-admin audio list --status pending # List pending analyses
sow-admin audio analyze --recording-id <id> # Submit for analysis
sow-admin audio status # Check analysis status
# Songset operations
sow-admin songset create --name "Sunday Worship" # Create new songset
sow-admin songset list # List all songsets
sow-admin songset export --songset-id <id> # Export to JSONCreate ~/.config/stream-of-worship-admin/config.toml:
[service]
analysis_url = "http://localhost:8000"
[database]
url = "postgresql://sow_admin_rw@ep-xxx-pooler.neon.tech/sow"
[r2]
bucket = "stream-of-worship"
endpoint_url = "https://<account-id>.r2.cloudflarestorage.com"
region = "auto"Note: Cache directory is always at ~/.cache/stream-of-worship-admin/ and is not configurable.
Required Environment Variables (for sensitive credentials):
# PostgreSQL password (Admin CLI - never store in config)
export SOW_DATABASE_PASSWORD="your-database-password"
# R2 credentials
export SOW_R2_ACCESS_KEY_ID="your-access-key"
export SOW_R2_SECRET_ACCESS_KEY="your-secret-key"
# Analysis service API key
export SOW_ANALYSIS_API_KEY="your-api-key"Note: Non-sensitive settings like database.url, r2.bucket, and r2.endpoint_url should be configured in the config file. Only sensitive credentials use environment variables for security.
# 1. Initialize the database
uv run --project ops/admin-cli --extra admin sow-admin db init
# 2. Scrape the song catalog from sop.org
uv run --project ops/admin-cli --extra admin sow-admin catalog scrape
# 3. Search for a specific song
uv run --project ops/admin-cli --extra admin sow-admin catalog search "主祢是愛"
# 4. Download audio for a song
uv run --project ops/admin-cli --extra admin sow-admin audio download --song-id "zhu-ni-shi-ai-1"
# 5. Submit for analysis (requires Analysis Service running)
uv run --project ops/admin-cli --extra admin sow-admin audio analyze --recording-id "abc123def456"
# 6. Check analysis status
uv run --project ops/admin-cli --extra admin sow-admin audio statusThe User App is the primary tool for worship leaders and media teams to create multi-song sets with synchronized lyrics videos.
Song Selection
- Browse master song catalog (from PostgreSQL database)
- Search by title, artist, album, key, tempo
- View song metadata and analysis results
- Preview audio stems (vocals, drums, bass, other)
Songset Creation
- Create multi-song sets with smooth transitions
- Adjust transition parameters:
- Crossfade duration
- Tempo matching (BPM alignment)
- Key shifting (musical key alignment)
- Gap duration between songs
- Real-time preview
Lyrics Video Generation
- Load LRC files from cloud storage
- Select video templates (dark, gradient_warm, gradient_blue)
- Configure resolution (720p, 1080p, 1440p, 4K)
- Export MP4 video with synchronized lyrics
Create ~/.config/stream-of-worship/config.toml:
[database]
url = "postgresql://sow_app@ep-xxx-pooler.neon.tech/sow"
[r2]
bucket = "stream-of-worship"
endpoint_url = "https://<account-id>.r2.cloudflarestorage.com"
region = "auto"
[app]
working_dir = "~/stream-of-worship"
preview_volume = 0.8
default_gap_beats = 2.0
default_video_template = "dark"
default_video_resolution = "1080p"Derived paths (User App only):
- Logs:
<working_dir>/logs/ - Output:
<working_dir>/output/ - Backup:
<working_dir>/backup/
Cache locations (not configurable):
- Admin CLI:
~/.cache/stream-of-worship-admin/ - User App:
~/.cache/stream-of-worship/
Required Environment Variables (for sensitive credentials):
# PostgreSQL password (required - env var only for security)
export SOW_DATABASE_PASSWORD="your-database-password"
# R2 credentials (required for downloading audio assets)
export SOW_R2_ACCESS_KEY_ID="your-access-key"
export SOW_R2_SECRET_ACCESS_KEY="your-secret-key"Note: The PostgreSQL password is read from the SOW_DATABASE_PASSWORD environment variable only and is never stored in the config file for security.
# Run the interactive TUI
sow-app run
# Run with custom config
sow-app run --config /path/to/config.toml
# Database operations
sow-app db check # Check database connectivity
# Songset operations
sow-app songset list # List all songsets
sow-app songset create "Worship Set" # Create new songset
sow-app songsets backup <id> # Backup songset to JSON
sow-app songsets restore <file> # Restore songset from JSON
# Configuration
sow-app config show # Display current configuration# 1. Run the TUI
uv run --project lab/sow-app sow-app run
# 2. In the TUI:
# - Browse catalog (press 'b')
# - Search for songs
# - Select songs to add to songset
# - Adjust transition parameters
# - Preview transitions
# - Export audio or video
# 3. Backup songset for sharing
uv run --project lab/sow-app sow-app songsets backup <songset-id>The Transition Builder is a mature, standalone tool for creating transition audio files between two songs. Unlike sow-app, it works directly with analysis JSON files and does not require a database.
Best for: Quick experimentation, 2-song transitions without database setup.
Prerequisites: Requires POC analysis output. See DEVELOPER.md for POC analysis setup instructions.
Create ~/.local/share/sow/config.json (Linux) or ~/Library/Application Support/sow/config.json (macOS):
{
"audio_folder": "~/.local/share/sow/song_library",
"output_folder": "~/.local/share/sow/output/transitions",
"output_songs_folder": "~/.local/share/sow/output/songs",
"analysis_json": "lab/poc-scripts/output_allinone/poc_full_results.json",
"stems_folder": "lab/poc-scripts/output_allinone/stems",
"audio_format": "ogg",
"audio_bitrate": "192k",
"video_resolution": "1080p"
}# Install with TUI dependencies
uv sync --extra tui
# Launch Transition Builder
uv run stream-of-worship tui
# Or with custom config path
uv run stream-of-worship tui --config /path/to/config.json- Select Outgoing Song - Choose the first song (where transition starts)
- Select Incoming Song - Choose the second song (where transition ends)
- Adjust Parameters:
- Crossfade duration
- Tempo matching (BPM alignment)
- Key shifting (musical key alignment)
- Transition point selection
- Preview - Listen to the transition
- Generate - Export the transition audio file
Problem: "Cannot connect to Docker daemon"
# Solution: Start Docker Desktop application
# Wait for it to fully start (whale icon in system tray)Problem: "Port 8888 already in use"
# Solution: Stop existing Jupyter instance or change port
docker compose -f docker/docker-compose.yml down
# Edit docker/docker-compose.yml: Change "8888:8888" to "8889:8888"
docker compose -f docker/docker-compose.yml upProblem: "librosa.load() fails with codec error"
# Solution: Convert audio to supported format
ffmpeg -i input.m4a output.mp3
# Or use FLAC: ffmpeg -i input.m4a output.flacProblem: "No audio files found"
# Solution: Check file location
ls poc_audio/
# Should show *.mp3 or *.flac files
# If empty, copy files: cp /path/to/songs/*.mp3 poc_audio/- Developer Documentation: DEVELOPER.md - Architecture, roadmap, advanced configuration
- User Guide: USER_GUIDE.md - Worship leader workflows, including offline playback
- Offline Worship Design: docs/offline_worship_design_explained.md - Service worker caching and the offline playback request flow
- POC Script Guide: lab/poc-scripts/README.md
- Design Document: specs/worship-music-transition-system-design.md
- librosa Documentation: https://librosa.org/doc/latest/
- Stream of Praise: https://www.sop.org/
Feedback welcome on:
- Analysis accuracy
- Additional test songs
- Edge cases or failure modes
See DEVELOPER.md for development setup and contribution guidelines.
MIT License - See LICENSE file
Last Updated: 2026-09-19