Architecture Overview
CSV to QLab is built with a configuration-driven architecture that makes it easy to add support for new QLab OSC properties without modifying code.
System Architecture
CSV File Upload
↓
CSV Parser (csv_parser.py)
↓
OSC Config Loader (osc_config.py)
↓
OSC Message Builder
↓
UDP Client (python-osc) → QLab (port 53000)
↓
OSC Server (port 53001) ← QLab Replies
↓
Error/Success Handler
↓
User Feedback (Flask UI)
Core Components
1. Entry Points
GUI Entry Point (run_gui.py
):
- Entry point for PyInstaller GUI builds
- Imports
app.application
as a module - Creates PyWebView window
- Located in project root (outside
app/
package)
CLI Entry Point (app/cli.py
):
- Command-line interface entry point
- Installed via
pip install .
ascsv-to-qlab
command - Provides automation and scripting capabilities
2. Flask Application (app/application.py
)
- Web server providing the UI
- Runs inside a PyWebView native window
- Handles file uploads and form submission
- Routes:
/
(upload),/success
(results) - Uses relative imports as part of the
app
package
3. CSV Parser (app/csv_parser.py
)
The main processing pipeline:
- Parse CSV - Reads CSV file into list of dictionaries
- Normalize headers - Converts to lowercase, removes spaces
- Validate cue types - Checks against valid types in config
- Build OSC bundles - For each cue:
- Create
/new {cue_type}
message - Build property messages using config
- Handle auto-properties (e.g., fadeopacity → doopacity)
- Create
- Send to QLab - UDP transmission with async reply handling
4. OSC Configuration System (app/osc_config.py
)
Configuration-Driven Design:
- All OSC properties defined in
qlab_osc_config.json
- No hardcoded OSC addresses in business logic
- Easy to add new properties or cue types
Key Methods:
get_property_config(property_name, cue_type, qlab_version)
# Returns config for a property
build_osc_message(property_name, value, cue_type, qlab_version)
# Builds OSC message from config
get_auto_properties(property_name, cue_type)
# Returns properties to auto-enable
5. OSC Server (app/osc_server.py
)
- Async UDP server listening on port 53001
- Receives QLab reply messages
- Parses JSON responses for status
- Routes to error/success handlers
Package Structure
CSV to QLab follows Python best practices for a pip-installable package with PyInstaller GUI:
csv_to_qlab/
├── run_gui.py # GUI entry point (PyInstaller)
├── setup.py # pip installation config
├── application.spec # PyInstaller build config
└── app/ # Main Python package
├── __init__.py # Package marker
├── cli.py # CLI entry point
├── application.py # Flask app
├── csv_parser.py # CSV processing
├── osc_config.py # OSC configuration
├── osc_server.py # OSC server
├── helper.py # Utilities
├── error_success_handler.py # Error tracking
├── qlab_osc_config.json # OSC property definitions
└── tests/ # Test suite
Import Structure:
- All modules within
app/
use relative imports (e.g.,from .csv_parser import send_csv
) - Entry points (
run_gui.py
,setup.py
) use absolute imports (e.g.,from app.cli import main
) - This follows PEP 8 and PyInstaller best practices
6. PyWebView Desktop Wrapper
- Creates native macOS app window
- Frameless design (300x465px)
- Bundles with PyInstaller for distribution
Data Flow Example
CSV Input:
Number,Type,Name,Follow,Color
1,audio,Main Music,2,blue
Processing:
- Parse:
{'number': '1', 'type': 'audio', 'name': 'Main Music', 'follow': '2', 'color': 'blue'}
- Build Bundle:
/new
→"audio"
/cue/selected/number
→"1"
/cue/selected/name
→"Main Music"
/cue/selected/continueMode
→2
(from config: follow → continueMode)/cue/selected/colorName
→"blue"
- Send bundle via UDP
- Receive reply:
{"status": "ok", "workspace_id": "..."}
- Display success
Configuration Schema
Global Properties
Available for all cue types:
{
"property_name": {
"osc_address": "/cue/selected/...",
"type": "int|float|bool|string",
"description": "Human-readable description",
"valid_range": [min, max], // optional
"valid_values": ["...", "..."] // optional
}
}
Cue-Type Properties
Specific to certain cue types:
{
"cue_type_properties": {
"audio": {
"level": {
"osc_address": "/cue/selected/level",
"type": "float"
}
}
}
}
Version-Specific Properties
Handle QLab 4 vs 5 differences:
{
"network": {
"qlab5": { /* v5 properties */ },
"qlab4": { /* v4 properties */ }
}
}
Auto-Property System
Some properties automatically enable related settings. For example:
User sets: Fade Opacity = 0.5
System automatically adds:
Do Opacity = true
(enables the opacity checkbox)
This is configured with "auto_value": true
in the JSON config.
Validation
The system validates:
- Cue types - Must be in
valid_cue_types
array - Property ranges - Checked against
valid_range
if specified - Property values - Checked against
valid_values
if specified - Conditional properties - Only set if condition is met
Invalid values are silently skipped (message not sent).
Error Handling
Global Error/Success Tracking:
error_success_handler.py
maintains global lists- Each OSC reply is categorized as success or error
- Displayed to user after all cues processed
OSC Reply Format:
{
"workspace_id": "ABC123",
"address": "/new",
"status": "ok" // or error message
}
Adding New Features
To add support for a new QLab property:
- No code changes needed!
- Add property to
app/qlab_osc_config.json
- Documentation auto-generated from config
See Adding Properties Guide for details.
Technology Stack
Component | Technology | Purpose |
---|---|---|
Backend | Python 3.9+ | Core logic |
Web Framework | Flask 3.0.3 | HTTP server |
Desktop UI | PyWebView 5.1 | Native window |
OSC Protocol | python-osc 1.8.3 | QLab communication |
Build Tool | PyInstaller | macOS app bundling |
Docs | Docusaurus | Documentation site |
QLab Communication
Ports:
53000
- Send OSC messages to QLab (UDP)53001
- Receive QLab replies (UDP)53535
- QLab's plain text OSC listener (not used)
Connection Flow:
- Send
/connect {passcode}
if provided - Send
/alwaysReply 1
to enable replies (implicit) - Send cue creation bundles
- Receive status replies
- Close connection (auto-timeout after 61s on UDP)
Design Principles
- Configuration over Code - Properties defined in JSON, not Python
- Fail Gracefully - Invalid properties skipped, processing continues
- Minimal Dependencies - Small footprint, quick startup
- Type Safety - Validation at config level
- Version Agnostic - Same codebase supports QLab 4 & 5