From 868b59077f07b6d4d74499ee704050d5ef0b6c23 Mon Sep 17 00:00:00 2001 From: "Troll (Hermes Agent)" Date: Sun, 20 Sep 2026 16:11:42 +0000 Subject: [PATCH] Group genre dropdowns into non-selectable optgroup sections - Load grouped genre list (music_genres_grouped.txt) with section headings - Render headings as labels (visible, not selectable) - Add Disney under CHILDREN'S & HOLIDAY (was missing from grouped file) - Bump version to 0.9.3 --- README.md | 4 +- VERSION | 2 +- app.py | 14 +- helpers.py | 35 +++- lists/music_genres_grouped.txt | 294 +++++++++++++++++++++++++++++++++ templates/admin/request.html | 16 +- templates/request.html | 16 +- 7 files changed, 360 insertions(+), 21 deletions(-) create mode 100755 lists/music_genres_grouped.txt diff --git a/README.md b/README.md index fc42295..831874b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Theme Song Booth (MusicGPT Edition) -![Version](https://img.shields.io/badge/version-v0.9.2-blue) +![Version](https://img.shields.io/badge/version-v0.9.3-blue) 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 prompts via Hermes, queue generations through the MusicGPT API, and deliver final songs by email. @@ -42,7 +42,7 @@ The MusicGPT fork automates song generation: instead of manually creating songs | 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. | +| 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_grouped.txt` (a grouped file where section headings are rendered as non-selectable dropdown groups). 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/` | Confirmation page shown after a request is submitted. | | Order status | `/status` | Customers enter their email to see all their requests and statuses. | diff --git a/VERSION b/VERSION index 2003b63..965065d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.2 +0.9.3 diff --git a/app.py b/app.py index 1a2f120..d2bf7cc 100644 --- a/app.py +++ b/app.py @@ -60,7 +60,7 @@ from models import SCHEMA, get_requests_by_email, log_revision, list_revision_hi # Helpers (extracted from app.py to keep the route file manageable) from helpers import ( - MUSIC_GENRES, DECADES, parse_style_genre, build_style_genre, + GENRE_GROUPS, DECADES, parse_style_genre, build_style_genre, is_admin, require_admin, admin_password_ok, is_valid_email, allowed_file, upload_path, save_upload, apply_mp3_tags, @@ -186,13 +186,13 @@ def request_form(): pronouns = request.form.get('pronouns', '').strip() if not pronouns: flash('Please select your pronouns.', 'error') - return render_template('request.html', form=request.form, decades=DECADES, genres=MUSIC_GENRES), 400 + return render_template('request.html', form=request.form, decades=DECADES, genre_groups=GENRE_GROUPS), 400 if not decade: flash('Please select a decade.', 'error') - return render_template('request.html', form=request.form, decades=DECADES, genres=MUSIC_GENRES), 400 + return render_template('request.html', form=request.form, decades=DECADES, genre_groups=GENRE_GROUPS), 400 if not basic_style: flash('Please select a basic style.', 'error') - return render_template('request.html', form=request.form, decades=DECADES, genres=MUSIC_GENRES), 400 + return render_template('request.html', form=request.form, decades=DECADES, genre_groups=GENRE_GROUPS), 400 form_data = { 'name': request.form.get('name', '').strip(), 'email': request.form.get('email', '').strip().lower(), @@ -206,7 +206,7 @@ def request_form(): } if not is_valid_email(form_data['email']): flash('Please enter a valid email address.', 'error') - return render_template('request.html', form=form_data, decades=DECADES, genres=MUSIC_GENRES), 400 + return render_template('request.html', form=form_data, decades=DECADES, genre_groups=GENRE_GROUPS), 400 rid = create_request(**form_data) # Notify operator via ntfy when a new request comes in. @@ -251,7 +251,7 @@ def request_form(): flash('Your request has been submitted! Check your email soon.', 'success') return redirect(url_for('thanks', rid=rid)) - return render_template('request.html', form=None, decades=DECADES, genres=MUSIC_GENRES) + return render_template('request.html', form=None, decades=DECADES, genre_groups=GENRE_GROUPS) @app.route('/api/ping', methods=['GET']) @@ -1521,7 +1521,7 @@ def admin_request(rid): req=req, style_parts=parse_style_genre(req.get('style_genre')), decades=DECADES, - genres=MUSIC_GENRES, + genre_groups=GENRE_GROUPS, statuses=STATUS_LABELS, file_exists=file_exists, basename=basename, diff --git a/helpers.py b/helpers.py index ad2a175..89f0eab 100644 --- a/helpers.py +++ b/helpers.py @@ -50,9 +50,9 @@ from config import Config # Genre / decade helpers # --------------------------------------------------------------------------- -_GENRES_PATH = Path('/mnt/Storage/Music Genres.txt') +_GENRES_PATH = Path('/mnt/Storage/music_genres_grouped.txt') _DECADES_PATH = Path('/mnt/Storage/Decades.txt') -_FALLBACK_GENRES_PATH = Path(__file__).parent / 'lists' / 'music_genres.txt' +_FALLBACK_GENRES_PATH = Path(__file__).parent / 'lists' / 'music_genres_grouped.txt' _FALLBACK_DECADES_PATH = Path(__file__).parent / 'lists' / 'decades.txt' @@ -72,9 +72,38 @@ def _load_list(primary: Path, fallback: Path) -> list[str]: return _load_lines(fallback) -MUSIC_GENRES = _load_list(_GENRES_PATH, _FALLBACK_GENRES_PATH) +def _load_genre_groups(primary: Path, fallback: Path) -> list[dict]: + """Parse a grouped genre file into [{'label': str, 'genres': [str, ...]}, ...]. + + A blank line ends the current group; the first line after a blank line (or + the very first line) is the group heading, and every following line until + the next blank line is a genre under that heading. Headings are rendered as + non-selectable labels, so they are visible but not clickable. + """ + path = primary if primary.exists() else fallback + if not path.exists(): + return [] + groups = [] + current = None + for raw in path.read_text(encoding='utf-8').splitlines(): + line = raw.strip() + if not line: + current = None + continue + if current is None: + current = {'label': line, 'genres': []} + groups.append(current) + else: + current['genres'].append(line) + return groups + + +GENRE_GROUPS = _load_genre_groups(_GENRES_PATH, _FALLBACK_GENRES_PATH) DECADES = _load_list(_DECADES_PATH, _FALLBACK_DECADES_PATH) +# Flat list of every genre, derived from the grouped list so the two stay in sync. +MUSIC_GENRES = [g for group in GENRE_GROUPS for g in group['genres']] + def parse_style_genre(style_genre: str | None) -> dict: """ diff --git a/lists/music_genres_grouped.txt b/lists/music_genres_grouped.txt new file mode 100755 index 0000000..0e7e0cf --- /dev/null +++ b/lists/music_genres_grouped.txt @@ -0,0 +1,294 @@ +ROCK +Alternative Rock +Art Rock +Blues Rock +Britpop +Classic Rock +Folk Rock +Garage Rock +Glam Rock +Gothic Rock +Grunge +Hard Rock +Indie +Indie Rock +J-Rock +Krautrock +Math Rock +Neue Deutsche Welle +New Romantic +New Wave +Post-Rock +Power Pop +Progressive Rock +Psychedelic Rock +Rock +Rock & Roll +Rockabilly +Shoegaze +Soft Rock +Southern Rock +Space Rock +Stoner Rock +Surf Rock +Symphonic Rock +Trop Rock +Yacht Rock + +METAL +Black Metal +Death Metal +Deathcore +Doom Metal +Folk Metal +Glam Metal +Gothic Metal +Heavy Metal +Industrial Metal +Metal +Metalcore +Nu Metal +Power Metal +Progressive Metal +Symphonic Metal +Thrash Metal + +PUNK & EMO +Afropunk +Emo +Hardcore Punk +Midwest Emo +Pop Punk +Post-Hardcore +Post-Punk +Punk Rock +Screamo +Ska Punk + +POP +Adult Contemporary +Art Pop +Ballad +Baroque Pop +Bedroom Pop +Bubblegum Pop +C-Pop +Cantopop +City Pop +Dance Pop +Dream Pop +Electropop +Folk Pop +Indie Pop +J-Pop +K-Pop +Latin Pop +Piano Ballad +Pop +Pop Rock +Power Ballad +Synthpop + +HIP-HOP & RAP +Boom Bap +Cloud Rap +Conscious Hip-Hop +Crunk +Drill +G-Funk +Gangsta Rap +Grime +Hip-Hop +Latin Trap +Lo-Fi Hip-Hop +Miami Bass +Phonk +Trap +UK Drill + +R&B & SOUL +Alternative R&B +Doo-Wop +Funk +Motown +Neo-Soul +New Jack Swing +R&B +Slow Jam +Soul + +ELECTRONIC & DANCE +Acid House +Afro House +Amapiano +Ambient +Bass Music +Big Beat +Big Room House +Breakbeat +Chicago House +Chillout +Chillwave +Chiptune +Dance +Dark Ambient +Darkwave +Deep House +Detroit Techno +Disco +Downtempo +Drum & Bass +Dubstep +EBM +Electro +Electro House +Electroclash +Electronic +Eurobeat +Eurodance +Future Bass +Future Funk +Goa Trance +Happy Hardcore +Hardstyle +House +Hyperpop +IDM +Industrial +Italo Disco +Jungle +Lo-Fi +Melodic Techno +Minimal Techno +Moombahton +New Age +Nu-Disco +Progressive House +Progressive Trance +Psybient +Psytrance +Synthwave +Tech House +Techno +Trance +Trip-Hop +Tropical House +UK Garage +Uplifting Trance +Vaporwave + +JAZZ +Acid Jazz +Bebop +Big Band +Cool Jazz +Dixieland +Free Jazz +Jam Band +Jazz +Jazz Funk +Jazz Fusion +Latin Jazz +Ragtime +Smooth Jazz +Swing + +BLUES +Blues + +FOLK, COUNTRY & AMERICANA +Acoustic +Alt-Country +Americana +Bluegrass +Celtic +Country +Country Pop +Country Rock +Folk +Honky Tonk +Indie Folk +Irish Folk +Outlaw Country +Sea Shanty +Singer-Songwriter +Western Swing +Zydeco + +WORLD & REGIONAL +Afrobeat +Afrobeats +Bhangra +Bollywood +Highlife +Klezmer +Native American +Polka +Qawwali +World Music + +LATIN +Bachata +Bossa Nova +Corrido +Cumbia +Flamenco +Latin +Mariachi +Merengue +Norteño +Reggaeton +Regional Mexican +Salsa +Samba +Tango +Tejano + +REGGAE & CARIBBEAN +Calypso +Dancehall +Dub +Reggae +Roots Reggae +Ska +Soca + +CLASSICAL & ORCHESTRAL +Baroque +Chamber Music +Classical +Neoclassical +Opera +Orchestral + +CHRISTIAN & GOSPEL +Christian Hip-Hop +Christian Rock +Contemporary Christian +Gospel +Worship + +SOUNDTRACK, CINEMATIC & THEATRE +Anime +Cabaret +Cinematic +Musical Theatre +Soundtrack + +CHILDREN'S & HOLIDAY +Children's Music +Christmas +Disney +Lullaby + +EASY LISTENING & TRADITIONAL VOCAL +A Cappella +Barbershop +Chanson +Easy Listening +Lounge + +OTHER / EXPERIMENTAL +Avant-Garde +Experimental +Noise diff --git a/templates/admin/request.html b/templates/admin/request.html index 8f8f976..0c286f7 100644 --- a/templates/admin/request.html +++ b/templates/admin/request.html @@ -133,16 +133,24 @@ diff --git a/templates/request.html b/templates/request.html index 778f56c..d5b0c71 100644 --- a/templates/request.html +++ b/templates/request.html @@ -62,16 +62,24 @@