Add inline comments and update README for MusicGPT workflow

This commit is contained in:
Troll (Hermes Agent) 2026-08-10 23:45:28 +00:00
parent 2ec73dee0f
commit 530e9e53ff
5 changed files with 149 additions and 44 deletions

View file

@ -14,13 +14,14 @@ The MusicGPT fork automates song generation: instead of manually creating songs
2. [Customer-facing pages](#customer-facing-pages) 2. [Customer-facing pages](#customer-facing-pages)
3. [Operator / admin pages](#operator--admin-pages) 3. [Operator / admin pages](#operator--admin-pages)
4. [Status flow](#status-flow) 4. [Status flow](#status-flow)
5. [Settings page explained](#settings-page-explained) 5. [MusicGPT workflow](#musicgpt-workflow)
6. [Docker installation](#docker-installation) 6. [Settings page explained](#settings-page-explained)
7. [Environment variables](#environment-variables) 7. [Docker installation](#docker-installation)
8. [File layout](#file-layout) 8. [Environment variables](#environment-variables)
9. [Local development](#local-development) 9. [File layout](#file-layout)
10. [Common troubleshooting](#common-troubleshooting) 10. [Local development](#local-development)
11. [License / ownership](#license--ownership) 11. [Common troubleshooting](#common-troubleshooting)
12. [License / ownership](#license--ownership)
--- ---
@ -29,7 +30,7 @@ The MusicGPT fork automates song generation: instead of manually creating songs
1. A visitor fills out a short form at `/request`. 1. A visitor fills out a short form at `/request`.
2. The operator reviews the request in the admin dashboard and generates a prompt for MusicGPT (manually or via the `/api/prompt` Hermes callback). 2. The operator reviews the request in the admin dashboard and generates a prompt for MusicGPT (manually or via the `/api/prompt` Hermes callback).
3. The operator saves the title, style, and lyrics to the request. 3. The operator saves the title, style, and lyrics to the request.
4. The operator clicks **Generate A/B with MusicGPT**, selects a model, and waits for the webhook to complete. MusicGPT produces two MP3 versions, WAVs, and an album cover. 4. The operator clicks **Generate A/B with MusicGPT**, selects a model, and waits for MusicGPT to finish. MusicGPT produces two MP3 versions, optional WAVs, and an album cover.
5. 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. 5. 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.
6. After the customer approves and pays, the operator records the payment reference and delivers the selected MP3(s) — optionally with WAV files and the album cover — by email. 6. After the customer approves and pays, the operator records the payment reference and delivers the selected MP3(s) — optionally with WAV files and the album cover — by email.
7. Optional STEMS can be generated for an extra fee and delivered via a share link. 7. Optional STEMS can be generated for an extra fee and delivered via a share link.
@ -75,11 +76,11 @@ The selected pronouns are stored in the `pronouns` column and included in confir
| Page | Path | Purpose | | Page | Path | Purpose |
|------|------|---------| |------|------|---------|
| Login | `/admin/login` | Simple session-based login. Password comes from the `ADMIN_PASSWORD` environment variable. | | 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. | | Dashboard | `/admin` | Main queue. Filter by status and auto-refresh at a configurable interval. Includes a **Refresh MusicGPT Status** button to manually poll all in-flight tasks. |
| Request detail | `/admin/request/<id>` | Full control of one request: edit customer info, save prompt, copy Hermes callback, queue MusicGPT generation, view status/cost, view revision history, upload MP3 overrides, send preview, record payment, deliver files (MP3/WAV/cover), add operator notes, cancel generation, and generate STEMS. | | Request detail | `/admin/request/<id>` | Full control of one request: edit customer info, save prompt, copy Hermes callback, queue MusicGPT generation, poll a single MusicGPT task, view status/cost, view revision history, upload MP3 overrides, send preview, record payment, deliver files (MP3/WAV/cover), add operator notes, cancel generation, and generate STEMS. |
| Pricing | `/admin/pricing` | Configure fixed prices and up to 5 custom items. Also shows running MusicGPT cost totals in USD. | | Pricing | `/admin/pricing` | Configure fixed prices and up to 5 custom items. Also shows running MusicGPT cost totals in USD. |
| Sales | `/admin/sales` | Report of all delivered requests with customer details and Square payment references. | | 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, MusicGPT webhook URL, album cover delivery toggle, and system reset. | | 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, MusicGPT webhook URL, album cover delivery toggle, automatic MusicGPT polling toggle, and system reset. |
| Reset | `/admin/reset` | Clears all requests and uploaded files. Requires admin password confirmation. | | Reset | `/admin/reset` | Clears all requests and uploaded files. Requires admin password confirmation. |
--- ---
@ -103,6 +104,56 @@ pending → prompt_ready → songs_uploaded → awaiting_payment → paid → de
--- ---
## MusicGPT workflow
### Supported models
The available MusicGPT models are limited by the API key. The deployed key supports `v6` and `v6-pro`; `v6-pro` is the default. The model dropdown on `/admin/request/<id>` only shows models returned by the MusicGPT API for the configured key.
### Generating songs
1. On `/admin/request/<id>`, enter or paste the **Title**, **Style**, and **Lyrics**.
2. Click **Save Prompt**.
3. Choose a model and whether to include WAV files.
4. Click **Generate A/B with MusicGPT**.
The app queues two conversions (Version A and Version B) with MusicGPT. It stores the task ID, both conversion IDs, the webhook URL, and an estimated cost.
### Webhook
MusicGPT POSTs to `/api/musicgpt/webhook` when each conversion completes. The handler:
- Matches the payload to the request by `task_id` and `conversion_id`.
- Updates the request status and cost.
- Downloads album cover, MP3s, and WAVs when the payload includes URLs.
- Webhooks often arrive before audio URLs are ready; missing files are filled in by polling.
### Polling
Three ways to poll MusicGPT:
| Method | How | When to use |
|--------|-----|-------------|
| Automatic | Background job hits `/admin/musicgpt/autopoll` every 3 minutes | Set-and-forget; enabled by default and can be turned off in `/admin/settings`. |
| Dashboard refresh | **Refresh MusicGPT Status** button on `/admin` | Manual check of all in-flight tasks. |
| Per-request | **Poll MusicGPT** button on `/admin/request/<id>` | Force a single request to update now. |
All polling paths call `musicgpt_poll_status()` and run `download_musicgpt_outputs()` when the API reports `COMPLETED`, so MP3s/WAVs arrive without further operator action once they are available.
### Cost tracking
The `/admin/pricing` page shows aggregate MusicGPT costs in USD. The per-request cost is recorded from the webhook/poll payload (`conversion_cost`) when MusicGPT provides it; otherwise the queued estimate is shown. Stems are tracked separately by `stems_cost`.
### WAV delivery
A per-request checkbox **Include WAV files with delivery** stores `deliver_wav=1`. When enabled, WAV files are downloaded alongside MP3s and attached to the delivery email.
### Album cover
A global toggle in `/admin/settings` controls whether the generated album cover is attached to delivery emails. The cover is always downloaded and shown on the admin request page.
---
## Settings page explained ## Settings page explained
The `/admin/settings` page is split into functional sections: The `/admin/settings` page is split into functional sections:
@ -112,8 +163,7 @@ The `/admin/settings` page is split into functional sections:
### Hermes API key ### Hermes API key
- Displays whether a key is configured. - 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.
- The key is used by the `/api/prompt/<id>` callback and by the `/api/key-test` diagnostic endpoint.
- Copy this key into your Hermes skill or AI assistant config. - Copy this key into your Hermes skill or AI assistant config.
### Customer revision limit ### Customer revision limit
@ -134,8 +184,14 @@ The `/admin/settings` page is split into functional sections:
- **Send Test Email** verifies the configuration. - **Send Test Email** verifies the configuration.
### MP3 metadata defaults ### MP3 metadata defaults
- Artist, album, year, and comment tags applied automatically to uploaded MP3s. - Artist, album, year, and comment tags applied automatically to downloaded MP3s.
- The title tag is taken from the saved Suno prompt. - The title tag is taken from the saved Suno/MusicGPT prompt title.
### MusicGPT settings
- **API key configured** indicator.
- **Webhook URL** — shown for reference; sent per-conversion with every generation request.
- **Album cover delivery** — attach cover to delivery emails.
- **Automatic MusicGPT polling** — enable/disable the background poll that downloads ready files without operator action.
### Database maintenance ### Database maintenance
- **Health Check** — verifies all expected tables and columns exist. - **Health Check** — verifies all expected tables and columns exist.
@ -176,7 +232,7 @@ python init_db.py
``` ```
7. Point your reverse proxy at the host port you chose (default `127.0.0.1:8000`). 7. Point your reverse proxy at the host port you chose (default `127.0.0.1:8000`).
8. Visit `/admin/settings`, confirm the MusicGPT webhook URL, and configure the MusicGPT account webhook endpoint. 8. Visit `/admin/settings`, confirm the MusicGPT webhook URL, and configure the MusicGPT account webhook endpoint to the same URL.
9. Copy the Hermes API key to your Hermes skill / AI assistant. 9. Copy the Hermes API key to your Hermes skill / AI assistant.
10. Print or display a QR code pointing to `https://your-domain/request`. 10. Print or display a QR code pointing to `https://your-domain/request`.
@ -196,10 +252,10 @@ Persistent volumes keep the database and uploads safe across redeploys.
| Variable | Required | Default | Description | | 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. | | `APP_SECRET_KEY` | Yes | — | Long random string for Flask sessions and for encrypting stored settings such as the SMTP password. |
| `ADMIN_PASSWORD` | Yes | — | Password used to log in to `/admin`. | | `ADMIN_PASSWORD` | Yes | — | Password used to log in to `/admin`. |
| `SMTP_PASS` | Yes | — | Password for the SMTP account used to send customer emails. | | `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. | | `PUBLIC_BASE_URL` | Yes | — | Public HTTPS URL of the booth, e.g. `https://music.dionysismedia.ca`. Used in player links, emails, callback URLs, and the MusicGPT webhook URL. |
| `SMTP_HOST` | No | `mailroot8.namespro.ca` | SMTP server hostname. | | `SMTP_HOST` | No | `mailroot8.namespro.ca` | SMTP server hostname. |
| `SMTP_PORT` | No | `465` | SMTP server port. | | `SMTP_PORT` | No | `465` | SMTP server port. |
| `SMTP_USER` | No | `ai@hallsworth.ca` | SMTP username. | | `SMTP_USER` | No | `ai@hallsworth.ca` | SMTP username. |
@ -221,8 +277,9 @@ Persistent volumes keep the database and uploads safe across redeploys.
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `app.py` | Flask routes, helpers, email layer, runtime settings, MP3 tagging, rate limiting, database maintenance, Hermes callback, kiosk, pricing, and sales report. | | `app.py` | Flask routes, runtime settings, email layer, MP3 tagging, rate limiting, database maintenance, Hermes callback, kiosk, pricing, sales report, MusicGPT webhooks, polling, and file downloads. |
| `config.py` | Environment-variable based configuration with sensible defaults. | | `config.py` | Environment-variable based configuration with sensible defaults. |
| `helpers.py` | MusicGPT API client, polling, downloads, email helpers, MP3 metadata, settings persistence, and utility functions. |
| `models.py` | SQLite schema, CRUD helpers, and revision history. | | `models.py` | SQLite schema, CRUD helpers, and revision history. |
| `init_db.py` | Standalone script to create or migrate the database. | | `init_db.py` | Standalone script to create or migrate the database. |
| `templates/` | Jinja2 templates for customer pages, admin pages, and kiosk display. | | `templates/` | Jinja2 templates for customer pages, admin pages, and kiosk display. |
@ -242,7 +299,7 @@ cd /home/jess/workspace/booth-musicgpt
python3 -m venv .venv python3 -m venv .venv
.venv/bin/pip install -r requirements.txt .venv/bin/pip install -r requirements.txt
cp .env.example .env cp .env.example .env
# Edit .env and set APP_SECRET_KEY, ADMIN_PASSWORD, SMTP_PASS, PUBLIC_BASE_URL # Edit .env and set APP_SECRET_KEY, ADMIN_PASSWORD, SMTP_PASS, PUBLIC_BASE_URL, MUSICGPT_API_KEY
.venv/bin/python init_db.py .venv/bin/python init_db.py
.venv/bin/python -m flask --app app run --host=0.0.0.0 .venv/bin/python -m flask --app app run --host=0.0.0.0
``` ```
@ -265,6 +322,8 @@ Visit:
| 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`. | | 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. | | 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`. | | `/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`. |
| MusicGPT generation fails | API key lacks access to the selected model. | Only `v6` and `v6-pro` are supported by the current key; choose from the dropdown. |
| Webhook arrives but no files download | MusicGPT webhook fires before audio URLs are ready. | Wait for the automatic background poll, click **Poll MusicGPT** on the request, or use **Refresh MusicGPT Status** on the dashboard. |
--- ---

30
app.py
View file

@ -308,6 +308,10 @@ def musicgpt_webhook():
req = dict(row) req = dict(row)
# Determine which version this webhook belongs to. # Determine which version this webhook belongs to.
# MusicGPT sends a separate webhook for each conversion_id; version A is the
# first conversion queued and version B is the second. We store both IDs on
# the request so we can route the payload correctly and decide which file to
# overwrite when a later webhook arrives for the same version.
version = None version = None
if req.get('musicgpt_conversion_id_1') and conversion_id == req['musicgpt_conversion_id_1']: if req.get('musicgpt_conversion_id_1') and conversion_id == req['musicgpt_conversion_id_1']:
version = 'A' version = 'A'
@ -381,7 +385,15 @@ def musicgpt_webhook():
@app.route('/admin/musicgpt/autopoll', methods=['POST']) @app.route('/admin/musicgpt/autopoll', methods=['POST'])
def admin_musicgpt_autopoll(): def admin_musicgpt_autopoll():
"""Internal endpoint used by the cron job. Only runs refresh if auto-poll is enabled.""" """
Internal endpoint used by the cron job for automatic MusicGPT polling.
Unlike the manual dashboard refresh endpoint, this route first checks the
'musicgpt_autopoll' runtime setting. If automatic polling is disabled it
returns immediately without touching the MusicGPT API, so operators can
turn background automation on/off from /admin/settings without stopping
the cron job.
"""
redir = require_admin() redir = require_admin()
if redir: if redir:
return redir return redir
@ -392,7 +404,12 @@ def admin_musicgpt_autopoll():
@app.route('/admin/musicgpt/refresh', methods=['POST']) @app.route('/admin/musicgpt/refresh', methods=['POST'])
def admin_musicgpt_refresh(): def admin_musicgpt_refresh():
"""Manual dashboard action: poll all in-flight MusicGPT tasks and update statuses.""" """
Manual dashboard action: poll all in-flight MusicGPT tasks and update statuses.
This is intentionally separate from /admin/musicgpt/autopoll so operators can
always trigger a manual refresh even when automatic polling is disabled.
"""
redir = require_admin() redir = require_admin()
if redir: if redir:
return redir return redir
@ -423,7 +440,7 @@ def admin_musicgpt_refresh():
update_request(row['id'], **fields) update_request(row['id'], **fields)
updated += 1 updated += 1
elif status in ('FAILED', 'ERROR'): elif status in ('FAILED', 'ERROR'):
update_request(row['id'], musicgpt_status='FAILED', musicgpt_error=conversion.get('status_msg') or 'Polling reported failure') update_request(row['id'], musicgpt_status='FAILED', musicgpt_error=(conversion.get('status_msg') if isinstance(conversion, dict) else None) or 'Polling reported failure')
failed += 1 failed += 1
elif status: elif status:
update_request(row['id'], musicgpt_status=status) update_request(row['id'], musicgpt_status=status)
@ -434,7 +451,12 @@ def admin_musicgpt_refresh():
@app.route('/admin/musicgpt/poll/<int:rid>', methods=['POST']) @app.route('/admin/musicgpt/poll/<int:rid>', methods=['POST'])
def admin_musicgpt_poll_request(rid): def admin_musicgpt_poll_request(rid):
"""Poll a single MusicGPT task from the admin request page and download files if ready.""" """
Poll a single MusicGPT task from the admin request page and download files if ready.
This gives operators a way to force-update one request without waiting for the
dashboard refresh or the automatic background poll.
"""
redir = require_admin() redir = require_admin()
if redir: if redir:
return redir return redir

View file

@ -13,6 +13,7 @@ Environment variables (defaults shown):
- ADMIN_PASSWORD plain-text password for /admin login - ADMIN_PASSWORD plain-text password for /admin login
- PUBLIC_BASE_URL public URL customers use (e.g. https://booth.example.com) - PUBLIC_BASE_URL public URL customers use (e.g. https://booth.example.com)
- SMTP_PASS password for the SMTP account - SMTP_PASS password for the SMTP account
- MUSICGPT_API_KEY API key for MusicGPT generation and polling
Optional: Optional:
- BOOTH_NAME name in customer text and emails (default Trollgorithm Theme Songs) - BOOTH_NAME name in customer text and emails (default Trollgorithm Theme Songs)
- HOST_PORT docker-compose host-side port mapping (default 127.0.0.1:8000) - HOST_PORT docker-compose host-side port mapping (default 127.0.0.1:8000)
@ -27,6 +28,9 @@ Environment variables (defaults shown):
- SMTP_PORT outgoing mail server port (default 465) - SMTP_PORT outgoing mail server port (default 465)
- SMTP_USER SMTP login username (default ai@hallsworth.ca) - SMTP_USER SMTP login username (default ai@hallsworth.ca)
- SMTP_FROM From address for customer emails (default ai@hallsworth.ca) - SMTP_FROM From address for customer emails (default ai@hallsworth.ca)
- HERMES_API_KEY API key for the /api/prompt callback (optional, can be set in settings)
- MUSICGPT_DEFAULT_MODEL default model selected in the admin dropdown (default v6-pro)
- MUSICGPT_MODELS comma-separated list of models available to the API key (default v6,v6-pro)
""" """
import os import os
@ -101,7 +105,8 @@ class Config:
# MusicGPT API key (env only; never stored in repo). # MusicGPT API key (env only; never stored in repo).
MUSICGPT_API_KEY = os.environ.get('MUSICGPT_API_KEY', '') MUSICGPT_API_KEY = os.environ.get('MUSICGPT_API_KEY', '')
# Available MusicGPT generation models. # Available MusicGPT generation models. The deployed API key only supports
# v6 and v6-pro, so we keep the list explicit rather than making it dynamic.
MUSICGPT_MODELS = ['v6', 'v6-pro'] MUSICGPT_MODELS = ['v6', 'v6-pro']
MUSICGPT_DEFAULT_MODEL = os.environ.get('MUSICGPT_DEFAULT_MODEL', 'v6-pro') MUSICGPT_DEFAULT_MODEL = os.environ.get('MUSICGPT_DEFAULT_MODEL', 'v6-pro')

View file

@ -342,19 +342,19 @@ def get_musicgpt_api_key():
def get_musicgpt_default_model(): def get_musicgpt_default_model():
"""Return the configured default MusicGPT model.""" """Return the configured default MusicGPT model, falling back to a supported model."""
default = current_app.config.get('MUSICGPT_DEFAULT_MODEL', 'v6-pro') default = current_app.config.get('MUSICGPT_DEFAULT_MODEL', 'v6-pro')
models = get_musicgpt_models() models = get_musicgpt_models()
return default if default in models else models[-1] return default if default in models else models[-1]
def get_musicgpt_models(): def get_musicgpt_models():
"""Return the list of supported MusicGPT models.""" """Return the list of supported MusicGPT models (filtered by API key capability)."""
return list(current_app.config.get('MUSICGPT_MODELS', ['v6', 'v6-pro'])) return list(current_app.config.get('MUSICGPT_MODELS', ['v6', 'v6-pro']))
def build_musicgpt_webhook_url(): def build_musicgpt_webhook_url():
"""Build the public webhook URL for MusicGPT async callbacks.""" """Build the public webhook URL for MusicGPT async callbacks from PUBLIC_BASE_URL."""
base = current_app.config.get('PUBLIC_BASE_URL', '').rstrip('/') base = current_app.config.get('PUBLIC_BASE_URL', '').rstrip('/')
return f"{base}/api/musicgpt/webhook" return f"{base}/api/musicgpt/webhook"
@ -539,6 +539,11 @@ def set_musicgpt_autopoll_enabled(enabled):
cfg['musicgpt_autopoll'] = bool(enabled) cfg['musicgpt_autopoll'] = bool(enabled)
save_booth_settings(cfg) save_booth_settings(cfg)
# ---------------------------------------------------------------------------
# MusicGPT API client
# ---------------------------------------------------------------------------
MUSICGPT_API_BASE = "https://api.musicgpt.com/api/public" MUSICGPT_API_BASE = "https://api.musicgpt.com/api/public"
@ -587,6 +592,9 @@ def musicgpt_generate_request(rid, title, music_style, lyrics, gender=None, mode
def musicgpt_poll_status(task_id): def musicgpt_poll_status(task_id):
""" """
Poll the MusicGPT API for a generation task status. Poll the MusicGPT API for a generation task status.
MusicGPT's /v1/byId endpoint returns the whole task record, including the
'conversion' object with the current status and any available audio URLs.
Returns a dict with keys: status, message, conversion, or error. Returns a dict with keys: status, message, conversion, or error.
""" """
url = f"{MUSICGPT_API_BASE}/v1/byId" url = f"{MUSICGPT_API_BASE}/v1/byId"
@ -640,6 +648,12 @@ def _download_file(url, dest):
def download_musicgpt_outputs(req, data, version=None): def download_musicgpt_outputs(req, data, version=None):
""" """
Download the MP3 and WAV outputs from a completed MusicGPT webhook/poll payload. Download the MP3 and WAV outputs from a completed MusicGPT webhook/poll payload.
MusicGPT returns audio URLs under different keys depending on whether the
payload is for one conversion or the combined task result. This function
normalizes those keys, downloads each available file to the request upload
directory, applies MP3 metadata tags, and updates the request row.
Updates the request row with local paths and returns a dict of saved paths. Updates the request row with local paths and returns a dict of saved paths.
`data` is the conversion dict from the API. For per-conversion webhooks, `data` is the conversion dict from the API. For per-conversion webhooks,
pass `version='A' or 'B'`. For combined payloads, version is auto-detected pass `version='A' or 'B'`. For combined payloads, version is auto-detected
@ -650,7 +664,7 @@ def download_musicgpt_outputs(req, data, version=None):
song_title = req.get("suno_title") or req.get("title") song_title = req.get("suno_title") or req.get("title")
saved = {} saved = {}
# Map version label to field names. # Map version label to the database column names used to store local paths.
def _fields(v): def _fields(v):
return ("song_a_path", "song_a_wav_path") if v == "A" else ("song_b_path", "song_b_wav_path") return ("song_a_path", "song_a_wav_path") if v == "A" else ("song_b_path", "song_b_wav_path")
@ -674,6 +688,9 @@ def download_musicgpt_outputs(req, data, version=None):
url_key = "conversion_path_1" if v == "A" else "conversion_path_2" url_key = "conversion_path_1" if v == "A" else "conversion_path_2"
wav_key = "conversion_path_wav_1" if v == "A" else "conversion_path_wav_2" wav_key = "conversion_path_wav_1" if v == "A" else "conversion_path_wav_2"
# Also support per-conversion webhook keys without _1/_2 suffix. # Also support per-conversion webhook keys without _1/_2 suffix.
# When MusicGPT sends one webhook per conversion it usually uses the plain
# 'conversion_path' / 'conversion_path_wav' keys; we still need to know
# whether to treat it as version A or B. The caller-provided version helps.
if not (data.get(url_key) or data.get(wav_key)): if not (data.get(url_key) or data.get(wav_key)):
if v == "A" or version == "A": if v == "A" or version == "A":
mp3_url = data.get("conversion_path") mp3_url = data.get("conversion_path")

View file

@ -10,6 +10,8 @@ Schema overview (see SCHEMA constant):
- requests table stores customer data, generated prompts, file paths, - requests table stores customer data, generated prompts, file paths,
approval state, email timestamps, payment reference, player token, approval state, email timestamps, payment reference, player token,
vocal gender preference, revision count, revision note, and operator notes. vocal gender preference, revision count, revision note, and operator notes.
- MusicGPT integration fields store task/conversion IDs, status, cost, error,
album cover URL, WAV paths, and stems task/cost/URL.
- Revisions: when a customer requests changes, the current A/B MP3 files are - Revisions: when a customer requests changes, the current A/B MP3 files are
renamed to archived "RevN-" copies and new versions are uploaded later. renamed to archived "RevN-" copies and new versions are uploaded later.
- operator_notes is an internal column for the booth team and is never shown - operator_notes is an internal column for the booth team and is never shown
@ -54,21 +56,21 @@ CREATE TABLE IF NOT EXISTS requests (
operator_notes TEXT, operator_notes TEXT,
stems_link TEXT, stems_link TEXT,
stems_interest INTEGER DEFAULT 0, stems_interest INTEGER DEFAULT 0,
musicgpt_task_id TEXT, musicgpt_task_id TEXT, -- MusicGPT Music AI task ID shared by Version A and B
musicgpt_conversion_id_1 TEXT, musicgpt_conversion_id_1 TEXT, -- conversion ID for Version A
musicgpt_conversion_id_2 TEXT, musicgpt_conversion_id_2 TEXT, -- conversion ID for Version B
musicgpt_status TEXT, musicgpt_status TEXT, -- IN_QUEUE / IN_PROGRESS / COMPLETED / FAILED
musicgpt_cost REAL, musicgpt_cost REAL, -- API-reported cost in USD credits
musicgpt_error TEXT, musicgpt_error TEXT, -- error message from MusicGPT or polling
album_cover_url TEXT, album_cover_url TEXT, -- URL to generated album cover image
song_a_wav_path TEXT, song_a_wav_path TEXT, -- local path to Version A WAV (if deliver_wav)
song_b_wav_path TEXT, song_b_wav_path TEXT, -- local path to Version B WAV (if deliver_wav)
deliver_wav INTEGER DEFAULT 0, deliver_wav INTEGER DEFAULT 0, -- 1 if WAV files should be delivered with MP3s
stems_task_id TEXT, stems_task_id TEXT, -- MusicGPT Extraction task ID for optional stems
stems_status TEXT, stems_status TEXT, -- status of the Extraction job
stems_cost REAL, stems_cost REAL, -- API-reported stems cost in USD credits
stems_url TEXT, stems_url TEXT, -- download / share link for stems
stems_error TEXT stems_error TEXT -- error message from stems job
); );
CREATE INDEX IF NOT EXISTS idx_requests_status ON requests(status); CREATE INDEX IF NOT EXISTS idx_requests_status ON requests(status);