| lists | ||
| static | ||
| templates | ||
| .env.example | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| app.py | ||
| config.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| init_db.py | ||
| models.py | ||
| README.md | ||
| requirements.txt | ||
| REVIEW.md | ||
| VERSION | ||
Theme Song Booth
Version: v0.5.8
A Flask web application for running a convention booth where visitors request a custom AI-generated theme song. Operators manage the queue from an admin dashboard, generate Suno prompts, upload MP3 previews, collect payment, and deliver final songs by email.
Table of contents
- What the booth does
- Customer-facing pages
- Operator / admin pages
- Status flow
- Settings page explained
- Docker installation
- Environment variables
- File layout
- Local development
- Common troubleshooting
- License / ownership
What the booth does
- A visitor fills out a short form at
/request. - The operator reviews the request in the admin dashboard and generates a Suno Custom Mode prompt.
- The operator (or an AI assistant via the
/api/promptcallback) saves the prompt to the request. - The operator creates two song versions in Suno, downloads them, and uploads Version A and Version B to the request page.
- The operator sends a preview email; the customer visits their private player page, listens to both versions, and either approves one/both or requests changes.
- After the customer approves and pays, the operator records the payment reference and delivers the selected MP3(s) by email.
- Optional stems / extras can be delivered via a share link that appears on the player page after delivery.
Customer-facing pages
| Page | Path | Purpose |
|---|---|---|
| Request form | /request |
Visitors enter email, name, pronouns, hobbies, notable facts, and extra requests. The music style is picked from three dropdowns: Decade (required), Basic style (required), and Additional style (optional). Decades and genres are loaded from /mnt/Storage/Decades.txt and /mnt/Storage/Music Genres.txt. Rate limited to 5 submissions per minute per IP. |
| Closed page | /request (when booth is closed) |
Shows a friendly closed banner instead of the form when the operator marks the booth closed. |
| Thanks | /thanks/<id> |
Confirmation page shown after a request is submitted. |
| Order status | /status |
Customers enter their email to see all their requests and statuses. |
| FAQ | /faq |
Answers common customer questions. |
| Private player | /play/<token> |
Secret link emailed to the customer. Streams Version A and B, lets them approve or request revisions, and later download delivered files / stems. |
| Kiosk | /kiosk |
Public full-screen display for a booth tablet. Cycles between a QR code for /request and the configured price list. Updates automatically when pricing or booth state changes. |
Style selection
The request form no longer has a free-text genre field. Instead, customers choose:
- Decade / era — required (e.g.
1980's). - Basic style — required (e.g.
Pop). - Additional style — optional (e.g.
Funk).
These are stored together in the style_genre column as a comma-separated string (e.g. 1980's, Pop, Funk) so no schema change is required. The admin request page shows the same dropdowns for corrections, and the copy-to-Hermes prompt formats the style as a clean sentence like "1980's-era Pop with Funk influences" for better Suno results.
Pronouns
A required Pronouns dropdown is shown just below the name field, with options:
- He/Him/His
- She/Her/Hers
- They/Them/Their
The selected pronouns are stored in the pronouns column and included in confirmation emails and Hermes prompt copy.
Operator / admin pages
| Page | Path | Purpose |
|---|---|---|
| Login | /admin/login |
Simple session-based login. Password comes from the ADMIN_PASSWORD environment variable. |
| Dashboard | /admin |
Main queue. Filter by status and auto-refresh at a configurable interval. |
| Request detail | /admin/request/<id> |
Full control of one request: edit customer info (including pronouns and structured style), save prompt, copy Hermes callback, view revision history, upload MP3s, send preview, record payment, deliver files, add operator notes, and cancel. |
| Pricing | /admin/pricing |
Configure fixed prices (one song, both songs, WAV per song, STEMs per song) and up to 5 custom items. |
| Sales | /admin/sales |
Report of all delivered requests with customer details and Square payment references. |
| Settings | /admin/settings |
Database health, backups, SMTP config, MP3 metadata defaults, revision limit, auto-refresh interval, kiosk mode, booth open/closed switch, Hermes API key management, and system reset. |
| Reset | /admin/reset |
Clears all requests and uploaded files. Requires admin password confirmation. |
Status flow
pending → prompt_ready → songs_uploaded → awaiting_payment → paid → delivered
| Status | Meaning |
|---|---|
pending |
Customer submitted; waiting for a Suno prompt. |
prompt_ready |
Prompt saved; ready to generate songs. |
songs_uploaded |
Both MP3s uploaded; preview link can be sent. |
revisions_requested |
Customer asked for changes; current files archived. |
awaiting_payment |
Customer approved a version; waiting for payment. |
paid |
Payment recorded. |
delivered |
Final MP3(s) emailed to the customer. |
cancelled |
Request cancelled by the operator. |
Settings page explained
The /admin/settings page is split into functional sections:
Booth state
- Booth open / closed — When closed,
/requestand/kioskshow the closed banner.
Hermes API key
- Displays whether a key is configured.
- Regenerate API Key creates a new random key stored in runtime settings.
- The key is used by the
/api/prompt/<id>callback and by the/api/key-testdiagnostic endpoint. - Copy this key into your Hermes skill or AI assistant config.
Customer revision limit
- Maximum number of times a customer can click Request Changes on the player page.
- Default is controlled by
MAX_REVISIONSenv var; can be overridden here.
Dashboard refresh
- How often
/adminreloads automatically (10, 20, or 30 seconds).
Kiosk display
- QR only — shows the QR code permanently.
- Pricing only — shows the price list permanently.
- Cycle every N seconds — alternates between QR and pricing.
SMTP settings
- Host, port, username, from address, and password for sending customer emails.
- The password is encrypted using
APP_SECRET_KEYbefore being saved. - Send Test Email verifies the configuration.
MP3 metadata defaults
- Artist, album, year, and comment tags applied automatically to uploaded MP3s.
- The title tag is taken from the saved Suno prompt.
Database maintenance
- Health Check — verifies all expected tables and columns exist.
- Fix Database Schema — adds missing tables/columns without deleting data.
- Download Database Backup — downloads the SQLite file.
- Restore Database Backup — replaces the live DB with an uploaded backup.
- Download Uploads Backup — ZIPs all uploaded MP3s for offline storage.
- System Reset — deletes all requests and uploaded files for a fresh event.
Docker installation
1. Prepare environment variables
Generate values for the required secrets:
python3 -c "import secrets; print(secrets.token_hex(32))"
Use the output for APP_SECRET_KEY.
2. Deploy with Portainer
- Log in to Portainer.
- Go to Stacks → Add stack.
- Choose Repository:
- URL:
https://gitlab.hallsworth.ca/yrtria/theme-song-booth.git - Branch:
main - Compose path:
docker-compose.yml
- URL:
- Add the environment variables listed in the section below.
- Deploy the stack.
- Open a console in the running
theme-song-boothcontainer and run once:
python init_db.py
- Point your reverse proxy at the host port you chose (default
127.0.0.1:8000). - Visit
/admin/settingsand click Regenerate API Key. - Copy the key to your Hermes skill / AI assistant.
- Print or display a QR code pointing to
https://your-domain/request.
Updating the deployment
After each push to Gitea:
Portainer → Stacks → theme-song-booth → Pull and redeploy
Persistent volumes keep the database and uploads safe across redeploys.
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
APP_SECRET_KEY |
Yes | — | Long random string for Flask sessions and for encrypting stored settings such as the SMTP password and legacy stored API key. |
ADMIN_PASSWORD |
Yes | — | Password used to log in to /admin. |
SMTP_PASS |
Yes | — | Password for the SMTP account used to send customer emails. |
PUBLIC_BASE_URL |
Yes | — | Public HTTPS URL of the booth, e.g. https://booth.dionysismedia.ca. Used in player links, emails, and callback URLs. |
SMTP_HOST |
No | mailroot8.namespro.ca |
SMTP server hostname. |
SMTP_PORT |
No | 465 |
SMTP server port. |
SMTP_USER |
No | ai@hallsworth.ca |
SMTP username. |
SMTP_FROM |
No | ai@hallsworth.ca |
From address for customer emails. |
BOOTH_NAME |
No | Trollgorithm Theme Songs |
Display name used in email subjects and page titles. |
HOST_PORT |
No | 127.0.0.1:8000 |
Host-side ip:port mapping for the container. |
INTERNAL_PORT |
No | 8000 |
Port gunicorn binds to inside the container. |
PRICE_PER_VERSION |
No | 10.00 |
Legacy price label shown in some templates; current pricing is configured from /admin/pricing. |
CURRENCY |
No | CAD |
Currency label shown with prices. |
MAX_REVISIONS |
No | 2 |
Default customer revision limit before an operator override. |
HERMES_API_KEY |
No | — | API key for the /api/prompt callback. If omitted, generate one from /admin/settings. |
DATABASE |
No | /app/data/booth.db |
Path to the SQLite database inside the container. |
UPLOAD_FOLDER |
No | /app/uploads |
Path to uploaded MP3 storage inside the container. |
File layout
| File | Purpose |
|---|---|
app.py |
Flask routes, helpers, email layer, runtime settings, MP3 tagging, rate limiting, database maintenance, Hermes callback, kiosk, pricing, and sales report. |
config.py |
Environment-variable based configuration with sensible defaults. |
models.py |
SQLite schema, CRUD helpers, and revision history. |
init_db.py |
Standalone script to create or migrate the database. |
templates/ |
Jinja2 templates for customer pages, admin pages, and kiosk display. |
static/ |
Banner images, closed banner, email logo, and kiosk QR code. |
lists/ |
Bundled copies of decades.txt and music_genres.txt used as fallback for the style dropdowns. |
Dockerfile |
Production container image definition. |
docker-compose.yml |
Portainer stack definition. |
requirements.txt |
Python dependencies. |
.env.example |
Local development environment template. |
Local development
cd /home/jess/workspace/theme-song-booth
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env
# Edit .env and set APP_SECRET_KEY, ADMIN_PASSWORD, SMTP_PASS, PUBLIC_BASE_URL
.venv/bin/python init_db.py
.venv/bin/python -m flask --app app run --host=0.0.0.0
Visit:
- Customer form: http://127.0.0.1:5000/request
- Admin login: http://127.0.0.1:5000/admin
- Kiosk: http://127.0.0.1:5000/kiosk
Common troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Emails not arriving | SMTP settings wrong or messages in spam. | Use Send Test Email on /admin/settings; verify host/port/password. |
| Can't reach app through domain | Reverse proxy points to wrong host port. | Match HOST_PORT to your proxy upstream. |
| Static banner not showing | Browser cached old image. | Hard-refresh or redeploy stack. |
| Logo missing from email | Logo file missing from static/. |
Ensure static/DM-Logo_email.png is in the container. |
| Database schema mismatch | New column/table added but old DB not migrated. | Go to /admin/settings and click Fix Database Schema, or run python init_db.py. |
| Kiosk shows old prices | Page auto-refreshes every 30s; check /admin/pricing. |
Verify pricing values and redeploy if templates changed. |
/api/prompt returns 401 |
Callback token expired or API key mismatch. | Copy a fresh callback URL from /admin/request/<id> and verify the key with /api/key-test. |
License / ownership
Built for Jess's Trollgorithm theme-song booth. All code and assets are private to that project.