Admin Login
Welcome to Event Setup. Get your competition weekend live in 4 simple steps.
π Before you continue:
1. Open your Scoring Program and go to Admin β Generate Emails. Save the Studio.txt file.
2. While in the Scoring Program, also save the "Program by Competition for Printer" PDF β you'll upload it in Step 2.
3. Go to the Import Studios tab and upload your Studio.txt to link studios to the event.
4. Once studios are imported, come back here and complete the steps below.
Choose the competition weekend you're setting up
The "Program by Competition for Printer" from your Scoring Program. This powers "Find Your Routine" on /live and validates your uploads.
Parsing PDF...
Add custom stage URLs, or leave empty to use the default starzdancecomp.com stream.
Leave empty = defaults to starzdancecomp.com livestream page. Supports Rumble, YouTube, Vimeo β any embed URL.
Should parents browsing /live be able to see uploaded photos & videos without an access code?
Your event is configured and ready.
QR Code URL (print & laminate)
media.turnthat.com/live
Paste your studio assignment text file. Auto-creates accounts, links to event, maps studio numbers.
Select an event
Select event + type, then drop your folder. Studio # subfolders auto-map using your imported studio list.
Media/Critiques: folder with studio # subfolders (1/, 2/, 3/...)
Awards: any files β goes to private admin storage
Upload shared photos visible to ALL studios and /live visitors. Drag a folder β subfolders preserved.
Drag & drop a folder here
Subfolders like "Awards" and "Improv" appear separately on /live
Uploading...
Drop your final compiled backup. Checks every file against S3 and the audit log β skips duplicates, skips files you intentionally deleted/renamed, and only uploads what's genuinely missing.
We'll scan everything, check audit log, and show you exactly what needs uploading.
Select File Manager tab to browse S3...
Navigate to the destination folder, then click "Move Here"
| Name | Contact | Created |
|---|
Manually link a studio and assign their folder number for uploads. This is the number that matches the folder name in your upload (e.g. folder "11" = studio #11).
| Name | Date | Location | Studios / Media | Status |
|---|
Configure what appears on the public event page (/live). People scan your QR code β land here.
Add stages for multi-room events. Leave empty to use default starzdancecomp.com stream. Works with Rumble, YouTube, Vimeo β any embed URL.
π QR Code Link
Print this as a QR code for your event β people scan it and get instant access to photos, videos, livestream, and schedule.
Send a branded email to every studio linked to an event. Includes their login URL, credentials, and instructions for sharing with families.
Parents subscribe on the /live page to get emailed when their kid's routine is coming up. Manage and reset here.
Create accounts for trusted employees. They can upload media and browse files for events you assign them to.
Loading...
Checks every routine for missing judge critiques (MP3 audio + MP4 video per judge). Shows exactly what's missing per studio, and lets you export HD videos for routines that need re-judging.
Find duplicate videos (-001 retries), corrupt files, and leftover .tmp files in one scan.
Run during or after each competition day. Checks every routine that should have performed by now β flags missing photos, missing videos, misfiled folders, and swapped numbers. Only checks routines scheduled up to the current time.
Upload a routine video and generate a rejudge link for a specific judge. The judge records their critique, and you get notified by email when it's done.
πΉ Drop video here or
Manage judge profiles, credentials, private info, and track earnings.
Loading...
Generate single-use codes for studios. Each code allows one free critique submission.
Click refresh to load codes.
All submitted critiques β queue, in progress, and completed.
Click refresh to load.
Controls where photos and videos are served from. Auto mode lets the system detect NAS availability.
When enabled, /live and /studio show a maintenance message instead of media.
Updated April 3, 2026 β Click any section to expand.
Server (AWS EC2):
πΉ Instance: t3.large (2 vCPU, 8GB RAM) β ~$60/month
πΉ Elastic IP: 3.131.5.193
πΉ OS: Ubuntu 22.04 / PHP 8.1 / MySQL / Nginx
πΉ SSL: Let's Encrypt auto-renew via certbot
πΉ ffmpeg: Installed for re-judge video merging
πΉ Web root: /var/www/starz/public/
πΉ Config: /var/www/starz/config.php
πΉ DB: turnt_arena (shared with TURNT Arena, starz_ prefix tables)
Storage β S3:
πΉ Bucket: starz-media-vault (us-east-2)
πΉ CloudFront CDN: d1udxlxsuq202.cloudfront.net (backup only β NAS is primary)
πΉ All viewing AND downloads go through NAS. Uploads go direct to S3. PDFs use presigned S3 URLs.
πΉ S3 egress: near zero β only PDFs, uploads, admin QA checks
Storage β NAS (Synology DS925+):
πΉ Hardware: 4Γ4TB WD Red Plus (SHR ~12TB usable) + 32GB RAM + 1TB WD Black NVMe
πΉ NIC: 2.5GbE (confirmed 2500Mb/s via ethtool)
πΉ Location: Home with T-Mobile 2Gbps fiber (static IP)
πΉ DSM: 7.3.2 β Web Station (PHP 8.1 enabled, nginx backend)
πΉ Public IP: 66.33.15.170 (T-Mobile static β confirmed not CGNAT)
πΉ Local IP: 192.168.1.134 (DHCP reservation)
πΉ Domain: files.turnthat.com β 66.33.15.170
πΉ SSL: Let's Encrypt on NAS (renewed April 3, 2026)
πΉ CloudSync: S3 β NAS (download only, syncs automatically)
πΉ Port forwards: 22, 80, 443, 5001 TCP β 192.168.1.134
πΉ Router: T-Mobile gateway at 192.168.1.1 (admin/a1b5c1ae, SSID CXNK01D2ADBE)
πΉ SSH: ssh Twisted701@66.33.15.170
πΉ QuickConnect: quickconnect.to/twisted701
πΉ Web Station service: "files" (native script language, PHP 8.1, nginx)
πΉ Web Station portal: 740033ca-0448-4243-9c33-4c7a8510d68b
πΉ Web Station PHP service: 28b01abb-817b-4d4d-9e83-79253fe9d701
πΉ CORS config: /usr/local/etc/nginx/conf.d/28b01abb-817b-4d4d-9e83-79253fe9d701/user.conf
πΉ ZIP script: /volume1/media/zip.php β builds ZIPs from local disk, streams directly to browser
πΉ Document root: /volume1/media
πΉ Timeouts: connection 60s, send 3600s, read 3600s (for large ZIP downloads)
πΉ Benchmark: EC2βNAS 155MB video in 1.4s = ~920 Mbps (limited by EC2 network, NAS can do 2.5Gbps)
πΉ Purpose: Serve ALL media viewing + downloads + ZIP downloads. Zero EC2 media bandwidth.
πΉ β οΈ CORS note: When Web Station service changes, CORS user.conf path changes. Check /var/tmp/nginx/test/plugin_config/ for current service ID.
DNS (GoDaddy):
πΉ media.turnthat.com β 3.131.5.193 (EC2)
πΉ files.turnthat.com β 66.33.15.170 (NAS)
πΉ play.turnthat.com β 3.131.5.193 (TURNT Arena)
πΉ book.turnthat.com β 3.131.5.193
β οΈ Costs (after NAS migration):
πΉ EC2 t3.large: ~$60/month
πΉ S3 Storage (~863GB): ~$20/month
πΉ EC2 EBS: ~$15/month
πΉ S3 egress: ~$0-2/month (PDFs + admin QA only)
πΉ Total: ~$95-100/month (was $150+ with CloudFront + S3 egress)
Pre-Setup:
π Admin β Generate Emails β save Studio.txt
π Program by Competition for Printer β save schedule PDF
In Admin:
1οΈβ£ Import Studios β upload Studio.txt, set event label & year
2οΈβ£ Event Setup β upload schedule PDF β configure livestream β set media visibility
3οΈβ£ Set event status to Active (enables overlay auto-detect)
4οΈβ£ Upload Media β desktop app uploads to S3
5οΈβ£ QA Inspector β Daily Media Report + Critique Validator + Temp Cleanup
6οΈβ£ Notify Studios β sends login email via SendGrid
During Event:
π€ Scorer Station records critiques β uploads to S3
π‘ Backstage advances routines β Now Performing overlay updates
πΊ Production staff manages livestream embed URLs
Post-Event:
π Run QA report β fix misfiled videos, missing photos, timestamp mismatches
π€ Custom Judge Critiques β upload video, generate judge link
π§ Resend studio emails individually from Studio Activity
starz-media-vault/
_Deleted/ β soft-deleted files (auto-cleaned after 7 days)
2026/
Des Moines/
Studio Name(#1)/
Media/
0041/ β routine photos folder
IMG_101528.JPG
1-041 - 07 March 2026 - 05-18-36 PM.mp4 β video
Critiques/
Judge_1_RoutineName.mp3 β audio
Judge_1_RoutineName.mp4 β merged video (480p, 15% original audio)
scoring.pdf
Custom/ β custom judge critiques
StudioNum/
RoutineName.mp4 β uploaded HD video
_Deleted/
Daily Media Report:
πΉ Missing videos, missing photos, misfiled videos (wrong studio folder)
πΉ Timestamp mismatch detection (Β±60min window) with rename/keep options
πΉ Duplicate JPG detection across studios with overlap delete button
πΉ Wrong-date video detection (outside event dates Β±1 day)
πΉ Photo/video preview, delete buttons per issue
πΉ Confirmed scratches from starz_scratches table (gold) vs likely scratched (gray)
πΉ Ignore system saved to audit log
πΉ "β This IS #X β Keep It Here" button for false positives
πΉ folderByRoutine prefers correct studio per schedule when duplicates exist
Critique Validator:
πΉ 5-pass name matching + saved pairings
πΉ Skips confirmed scratches (loads from starz_scratches)
πΉ Per-routine list per judge with video-ready status
πΉ "β οΈ Upload video to enable rejudge" warnings
πΉ Generate rejudge links per judge with all missing routines
Video Checker:
πΉ Finds duplicate videos (original + -001 retry from ffmpeg)
πΉ Compare sizes, preview both, delete corrupt one
πΉ "Delete All Corrupt" bulk button
Temp File Cleanup:
πΉ Scan for .tmp files (including .tmp.mp4 from interrupted recordings)
πΉ Manual delete button + automatic hourly cron cleanup
Custom Judge Critique:
πΉ Select event β studio β type routine name β pick judge β upload HD video
πΉ Video uploads to S3: Year/EventLabel/Critiques/Custom/StudioNum/RoutineName.ext
πΉ Generates rejudge link (/rejudge/TOKEN)
πΉ Judge watches video, records critique β server merges via ffmpeg
πΉ Saves Judge_X_RoutineName.mp3 + .mp4 to studio's Critiques/ folder
πΉ Email notification via SendGrid when judge finishes
How It Works:
1οΈβ£ Admin runs Critique Validator β sees missing critiques per judge
2οΈβ£ Clicks "Generate Link for Judge X" β creates rejudge session in DB
3οΈβ£ Session contains: judge label, list of routines with videoKey references
4οΈβ£ Judge opens /rejudge/TOKEN on any device (phone, laptop, tablet)
Judge Flow:
πΉ Welcome screen β shows routine count, judge name
πΉ Mic Check β records 3 seconds, plays back for verification
πΉ Judging screen β video plays at FULL volume, mic records simultaneously
πΉ Review β listen to recording, redo or submit
πΉ Auto-advances to next routine
Server Processing (on submit):
πΉ Receives audio as WebM blob
πΉ Converts to MP3 via ffmpeg
πΉ Downloads video from S3 (uses precache β video downloaded while judge watches)
πΉ ffmpeg merges: original video (15% audio) + judge recording (100% audio) β 480p MP4
πΉ Uploads Judge_X_RoutineName.mp3 and .mp4 to Critiques/ folder on S3
πΉ Marks routine as completed in session
Custom Critique (single routine):
πΉ Admin uploads HD video via QA β Custom Judge Critique tool
πΉ Stored in starz_custom_critiques table (token, video key, studio, routine name)
πΉ Same judge flow β /rejudge/TOKEN works for both regular and custom
πΉ Info endpoint checks custom_critiques table first, then rejudge_sessions
πΉ Submit endpoint handles both β custom saves to studio's Critiques/ folder
πΉ Sends email via SendGrid when complete
Key Tables:
πΉ starz_rejudge_sessions: token, judge_label, routines (JSON), completed (JSON), status
πΉ starz_custom_critiques: token, event_id, studio_num, studio_name, routine_name, judge_num, video_s3_key, critique_mp3_key, critique_mp4_key, notify_email, status
Key Endpoints:
πΉ rejudge/TOKEN/info β session data (checks custom first, then regular)
πΉ rejudge/TOKEN/video-url β presigned S3 URL for video playback
πΉ rejudge/TOKEN/precache β downloads video to server cache while judge watches
πΉ rejudge/TOKEN/submit β receives audio, ffmpeg merge, upload to S3
Components:
πΉ /live/overlay.php β standalone overlay for vMix/OBS (1920Γ1080, transparent bg)
πΉ /live/embed.php β embeddable player with overlay for WordPress/external sites
πΉ /live page β overlay built into livestream viewer
Overlay Shows:
πΉ Now Performing: routine #, name, studio, division
πΉ Ahead/behind schedule timing
πΉ Up Next: next 2-3 routines
πΉ Custom text from backstage (e.g. "IMPROV")
πΉ Polls /api/public/now-performing every 3 seconds
Embed Page (embed.php):
πΉ Loads stream URL from event config (supports Rumble, YouTube)
πΉ Overlay sits below video (not on top β avoids blocking player controls)
πΉ Custom fullscreen button (top-right) β fullscreens video + overlay together
πΉ Hidden on mobile (mobile uses native Rumble fullscreen)
πΉ Auto-detects active event if no event_id specified
URLs:
πΉ /live/overlay.php?stage=A β vMix/OBS browser source
πΉ /live/embed.php?event_id=X&stage=A β WordPress embed
πΉ /live/embed.php?stage=A β auto-detect active event
Production Staff Login:
πΉ Username: production / Password: starz1 (desktop_role='production' in starz_staff)
πΉ Sees ONLY the livestream setup page β no admin, no QA, no files
πΉ Workflow: select event β paste Rumble/YouTube URL β save β copy embed code
πΉ Auto-converts Rumble page URLs and embed scripts to iframe URLs
πΉ WordPress instructions + copy button for iframe embed code
Event Status:
πΉ Events tab has status dropdown: Active / Upcoming / Completed / Cancelled
πΉ Active events show overlay links and embed code copy buttons
πΉ public/active-event endpoint returns the active event for auto-detect
Modes:
πΉ Upload Machine: uploads photos/videos/critiques from local folders to S3
πΉ Scorer Station: monitors judge critique recordings in real-time
πΉ Monitor: remote view of scorer status + scratch management
Upload Machine:
πΉ Select event β pick folder β scans files β S3 verification runs in BACKGROUND
πΉ UI is usable immediately β no blocking scan overlay
πΉ Status shows: "15,200 pending β verified 5,000/20,458"
πΉ Deleted files (via QA) return -1 from check-exists β skipped, not re-uploaded
πΉ Temp files (.tmp), files <5 seconds old, files with .tmp companion are skipped
πΉ Quarantine for small files: audio <500KB, video <2MB β play/upload/skip buttons
Scorer Station:
πΉ Maps 3 judge folders β monitors for new MP3/MP4 files every 10 seconds
πΉ Grid: Routine | Judge 1 | Judge 2 | Judge 3 (β /β/β οΈ per file type)
πΉ Smart alerts: 30-second timer before alerting missing judge files
πΉ Live activity ticker: color-coded per judge
πΉ Progress bar: "47 / 497 (499 scheduled, 2 scratched)"
πΉ Scratch system: dropdown from schedule, syncs to starz_scratches table
Build:
πΉ Electron app β npm start to run, npm run build for .exe
πΉ Source: src/index.html (UI), src/main.js (Node backend), src/preload.js (bridge)
/studio β Studio Portal:
πΉ Login with email/password β see events β media/critiques/codes
πΉ Photos grouped by routine with dancer names from schedule
πΉ Delete moves to _Deleted/ on S3, download via CloudFront/NAS
πΉ Access codes for parents with media/critiques/both filter
/gallery/CODE β Parent Access:
πΉ Same UI as studio but view/download only β no delete, no codes
πΉ Respects code expiration, use count, category filter
/live β Public Event Page:
πΉ QR code landing page: Photos, Videos, Livestream, Schedule, Find Routine, Notify Me
πΉ Livestream: single room goes straight to stream, multi-room shows picker
πΉ Only rooms with URLs configured are shown (empty URLs filtered out)
πΉ Now Performing overlay on livestream viewer
πΉ Stream iframe killed when navigating back (stops background audio)
πΉ Schedule PDF loaded on demand (presigned URL generated per-request)
πΉ Push notifications via web-push + SendGrid email
πΉ Browse S3 bucket with folder tree navigation
πΉ Search: search all S3 files by name (scans entire bucket, 30s timeout)
πΉ Move, rename, delete files (audit logged)
πΉ Bulk select + delete with progress
πΉ Delete moves to _Deleted/ (soft delete) β uploader skips deleted files
Hourly Cron (api/cron/cleanup?key=starz-cleanup-2026):
πΉ Deletes expired sessions (30-day lifetime)
πΉ Cleans rejudge video cache (/tmp/rejudge_cache/) older than 24 hours
πΉ Permanently deletes S3 _Deleted/ files older than 7 days
πΉ Deletes .tmp and .tmp.mp4 files from S3
πΉ Cleans old scorer status data (7+ days)
Server Crontab (root):
πΉ 0 * * * * curl -s "https://media.turnthat.com/api/cron/cleanup?key=starz-cleanup-2026"
Email: SendGrid
πΉ API key in config.php (SMV_SENDGRID_KEY)
πΉ Used for: studio notifications, custom critique completion, push notification emails
πΉ From: [email protected] / Starz Dance Competition
Tables:
πΉ starz_events, starz_studios, starz_event_studios, starz_event_studio_numbers
πΉ starz_sessions, starz_staff, starz_staff_events
πΉ starz_access_codes, starz_schedule_entries
πΉ starz_file_audit, starz_media (legacy)
πΉ starz_rejudge_sessions, starz_custom_critiques
πΉ starz_scratches (event_id, routine_num, routine_name)
πΉ starz_scorer_status (event_id, status_json)
πΉ starz_stage_queue, starz_notify_*, starz_room_chat
New Endpoints (this session):
πΉ public/active-event β returns current active event (for overlay auto-detect)
πΉ public/schedule-url β generates presigned URL for single event's schedule PDF
πΉ admin/set-event-status β change event status (active/upcoming/completed/cancelled)
πΉ admin/create-custom-critique β upload video + create rejudge link
πΉ admin/resend-studio-welcome β resend login email to single studio (test_mode param)
πΉ admin/files/search β search entire S3 bucket by filename
πΉ admin/presign-upload β alias for admin/upload-url (web admin bulk upload)
πΉ cron/cleanup β maintenance endpoint (key=starz-cleanup-2026)
Overview:
πΉ 4th button on /build home page β "π§ Email Studios"
πΉ Upload schedule PDF β pdfplumber parses all routines β groups by studio
πΉ Pulls studio emails from event-studio-map
πΉ Draft/Final version toggle, custom subject + message
πΉ Select all/none studios, send one at a time with progress bar
πΉ Test email sends to [email protected] with [TEST] prefix
πΉ Preview button opens popup with print-friendly styles
πΉ Session caching β remembers last parse on refresh
Per-Studio Email HTML:
πΉ Event header, studio name, routine count, version label
πΉ Routines grouped by Day/Room, sorted by time
πΉ Color-coded levels (S=gold, RS=green, NS=gray in email)
πΉ Award ceremony markers inline in schedule
πΉ TITLE badge (purple) for routines with + in category
πΉ Dancer names listed under each routine
πΉ Print styles: white bg, alternating gray rows, ink-friendly
Backend:
πΉ send_schedule_email action in /build backend (not /api/admin/)
πΉ SendGrid HTML email β no attachments, renders in inbox
πΉ Logs to starz_schedule_emails table
Category Parsing Fix:
πΉ COL_CATEGORY boundary set from "Grp" header right edge + 5px
πΉ Fixed "Contemporary" at x=268.6 being skipped by hard-coded x>270
πΉ cat_full field added as fallback β raw "Contemporary S 15-18" text
Overview:
πΉ Phone-based awards tracker for audience members during ceremonies
πΉ All data stored in localStorage β zero server load
πΉ Gold-bordered button on /live hub (only shows when schedule entries exist)
Selection Modes:
πΉ All Routines β track everything
πΉ Your Studio β pick studio from dropdown
πΉ Choose Routines β search/select specific routines
Two Tabs (matching award ceremony flow):
πΉ π Adjudication β Judges Award, G/HG/P/DE buttons, Category placement X/total
πΉ π High Points β routines grouped by HP pool in read order (youngestβoldest, SoloβProd, NSβRSβS)
πΉ HP tab has level filter: All / New Starz / Rising Starz / Starz
Per-Routine Tracking:
πΉ Judges Award β themed modal prompt, "JA" badge on header
πΉ Adjudication β G/HG/P (+ DE for Starz only), badge on header
πΉ Category Placement β X / total (auto-filled from block count, level-separated)
πΉ High Point Placement β X / top-N (5/10/15/20 from /build thresholds: 24+=10, 49+=15, 75+=20)
πΉ Overall Placement β X / total (RS and S only, not NS)
UX:
πΉ Cards collapsed by default β tap to expand, auto-closes others
πΉ Badges update in real-time on collapsed headers
πΉ atSave() merges with existing data (doesn't wipe other tab's data)
πΉ Themed modals replace native prompt()/confirm()
πΉ Sorting: R# Order or By Room/Day. Filters: Day + Room
Share Results:
πΉ π€ Share button generates trophy-case HTML with medal chips
πΉ html2canvas renders to PNG image at 2x resolution
πΉ Mobile: native share sheet with image file (Instagram/text/email)
πΉ Desktop: downloads PNG file
πΉ Responsive: single column on phone, 2-col tablet, 3-col print/desktop
πΉ Light theme for share (white cards, dark header, color chips)
Level Colors (brand):
πΉ New Starz: pink #d94b7a
πΉ Rising Starz: yellow #f5d623
πΉ Starz: blue #2196F3
Level Column:
πΉ starz_schedule_entries.level β VARCHAR(10), stores NS/RS/S
πΉ pdf.js parser (admin) can't reliably extract level from text items
πΉ Solution: after pdf.js save, /build pdfplumber parser runs on same PDF
πΉ Pdfplumber extracts level reliably β admin/update-schedule-levels UPDATEs the DB
πΉ This happens automatically in setupUploadSchedule() step 4
Division String Format (from pdf.js):
πΉ "Solo 1 Contemporary S 15-18" or "D/T 2 Jazz T 12-14"
πΉ "Producti" β pdf.js splits "Production" across text items
πΉ All regex patterns must handle: Producti(?:on)?|Prod|Production
HP Key Format:
πΉ AGE-DIVISION-LEVEL (e.g. "T-Solo-RS", "S-D/T-S")
πΉ Category block key: division string + "|" + level (separates NS/RS/S)
Copy this entire block and paste it into a new AI conversation to fully onboard an assistant.
Welcome! Set up the livestream for your event.
Paste your Rumble embed code, YouTube embed URL, or any stream URL. The system will automatically extract the correct link.
π WordPress Instructions:
Click to see what viewers see (stream + overlay):