Real-time DJ recommendation dashboard — load a track on deck 1 in Serato, instantly see the 10 best harmonically compatible tracks from your library.
Serato DJ Pro (deck 1)
→ serato_event_agent.py (Windows host, ReadDirectoryChangesW)
→ Kafka topic: serato.track.events (Redpanda broker)
→ Spark Structured Streaming (Delta Lake lookup + Camelot scoring)
→ Kafka topic: serato.recommendations
→ SSE → browser (sub-second latency)
- serato_event_agent.py watches
master.sqliteon the Windows host. Only deck 1 triggers the pipeline — deck 2 (preparation) is ignored. - Spark scores recommendations by harmonic key (Camelot Wheel), BPM, folder proximity, and acoustic similarity.
- Dashboard at
http://localhost:8050updates instantly via Server-Sent Events.
- Docker Desktop (Windows, with WSL2 backend)
- Python 3.10+ on the Windows host (for
serato_event_agent.py) - Serato DJ Pro 4+
- NVIDIA GPU (optional — only needed for acoustic embeddings)
# First run — build images (takes a few minutes)
docker compose build
# Start all services
docker compose up -d
# Check everything is up
docker compose psOpen a separate PowerShell terminal:
# Install dependencies once
pip install watchdog kafka-python
# Start the agent (keep this terminal open while mixing)
cd C:\Users\arnau\Documents\deckstream
python serato_event_agent.pyYou should see:
INFO Serato DB found — no active session yet (open Serato + load a track)
INFO Kafka producer connected to localhost:19092
INFO Watching : C:\Users\arnau\AppData\Local\Serato\Library
The agent will print:
INFO Track changed → Your Track Title
INFO → Produced to serato.track.events: /music/GENRE/track.mp3
Recommendations appear at http://localhost:8050 within ~1 second.
| URL | Service | Purpose |
|---|---|---|
| http://localhost:8050 | Dashboard | DJ view + library management |
| http://localhost:8080 | Spark UI | Spark cluster status |
| http://localhost:8090 | Kafka UI | Topics, consumer lag, message payloads, latency |
Every recommendation message on serato.recommendations contains:
agent_ts— when the agent detected the track changespark_ts— when Spark finished scoring
spark_ts − agent_ts = pipeline latency (typically 0.5–1.2 s).
# Normal start
docker compose up -d
# Rebuild after Dockerfile or dependency changes
docker compose build
docker compose up -d
# Force-recreate a specific service (picks up env var changes)
docker compose up -d --force-recreate dashboard
docker compose up -d --force-recreate stream-processor
# Restart streaming pipeline only
docker compose restart stream-processor
# Stop everything
docker compose down
# Follow logs
docker logs -f deckstream-dashboard
docker logs -f stream-processor
docker logs -f redpanda
# Check all container statuses
docker compose psAccess the dashboard from your phone or any device without port forwarding.
# Install Tailscale on Windows
winget install tailscale.tailscaleThen install Tailscale on your phone (App Store / Play Store) and log in with the same account.
- Find your PC's Tailscale IP at login.tailscale.com/admin/machines (format:
100.x.x.x) - On your phone:
http://100.x.x.x:8050
Requirements:
docker compose up -dmust be running on the Windows host- Tailscale must show "Connected" in the Windows system tray
- The Docker port binding must be
0.0.0.0:8050(not127.0.0.1) — checkdocker-compose.yml
Three phases — always run in order from the dashboard container:
# Phase 1 — scan audio files → extract metadata → Delta Lake
docker exec deckstream-dashboard python -m src.ingestion.cli ingest /music/HOUSE
# Phase 2 — generate acoustic embeddings (requires embedder container + GPU)
docker exec deckstream-dashboard python -m src.ingestion.cli embed /music/HOUSE
# Phase 3 — merge embeddings into Delta Lake
docker exec deckstream-dashboard python -m src.ingestion.cli merge
# Full pipeline in one command
docker exec deckstream-dashboard python -m src.ingestion.cli all /music/HOUSEOr use the Library page at http://localhost:8050/library — paste a path, click Ingest.
deckstream/
├── serato_event_agent.py # Windows host agent (ReadDirectoryChangesW → Kafka)
├── src/
│ ├── dashboard/
│ │ ├── app.py # App factory, clientside SSE callback
│ │ ├── sse.py # SeratoSSEBroadcaster (Kafka consumer + fallback)
│ │ ├── scoring.py # Hybrid additive scoring
│ │ ├── recommendation_engine.py # Camelot Wheel 7-transition engine
│ │ ├── delta_utils.py # PySpark Delta Lake queries + 3-layer cache
│ │ ├── serato_sqlite.py # WAL-safe Serato SQLite reader
│ │ ├── layout.py # All page layouts
│ │ ├── art.py # Album art cache
│ │ ├── colors.py # Design tokens
│ │ ├── assets/
│ │ │ ├── style.css # Glassmorphic design system
│ │ │ └── sse_client.js # Browser EventSource client
│ │ └── callbacks/
│ │ ├── current_track.py # Now Playing (fast path: Kafka / slow path: SQLite)
│ │ ├── recommendations.py # Recommendation rendering (fast path: Kafka payload)
│ │ ├── history.py # Play history
│ │ ├── library.py # Ingest / remove / embed jobs
│ │ └── routing.py # Page routing
│ ├── streaming/
│ │ └── spark_recommender.py # Spark Structured Streaming job
│ └── ingestion/
│ ├── cli.py # Entry point: ingest / embed / merge / all
│ ├── ingest.py # Bulk PySpark ingestion (IngestionRunner)
│ ├── embedder.py # Essentia discogs-effnet acoustic embeddings
│ ├── metadata_extractor.py # mutagen tag extraction (MP3, FLAC, WAV)
│ ├── hierarchy_parser.py # Path → party_type / genre / sub_genre / vibe
│ └── schema.py # Spark schemas + Pandera validation
├── docs/
│ ├── schemas/
│ │ ├── live-workflow.md # Mermaid: end-to-end live pipeline
│ │ └── ingestion-workflow.md # Mermaid: three-phase ingestion
│ ├── EN/ # English documentation
│ └── FR/ # French documentation
├── data/ # Delta Lake warehouse (git-ignored)
├── config/ # config.yaml (scoring weights, paths)
├── Dockerfile # Spark + dashboard + ingestion image
├── Dockerfile.embedder # Essentia GPU image
└── docker-compose.yml # Full stack definition
| Layer | Technology | Why |
|---|---|---|
| Event broker | Redpanda (Kafka-compatible) | Single container, no Zookeeper, ~512 MB RAM, <1 ms latency |
| Stream processor | Spark Structured Streaming | 500 ms micro-batch, Delta Lake integration, Camelot scoring |
| Storage | Delta Lake (PySpark) | ACID transactions, schema enforcement, time travel |
| Backend | Python 3.11, PySpark 3.5 | — |
| Frontend | Dash 4.1, Plotly | Reactive Python UI, SSE clientside callbacks |
| Live tracking | Serato SQLite (master.sqlite) |
Serato DJ 4+ drops binary .session files, uses SQLite |
| Acoustic similarity | Essentia discogs-effnet | 200-dim embedding, GPU inference |
| Containerisation | Docker, Docker Compose | — |
| Remote access | Tailscale (WireGuard) | Phone access without port forwarding |
| File | Content |
|---|---|
CLAUDE.md |
Full development guide — architecture, pitfalls, design decisions |
docs/schemas/live-workflow.md |
Mermaid diagram — live pipeline |
docs/schemas/ingestion-workflow.md |
Mermaid diagram — ingestion |
docs/EN/ARCHITECTURE.md |
System architecture + latency budget |
docs/EN/STREAMING.md |
Kafka/Redpanda/Spark deep dive (Netflix, Spotify, Uber, LinkedIn comparisons) |
docs/EN/NOW_PLAYING.md |
Live detection implementation details |
docs/EN/TECH_STACK.md |
Every tool and why it's there |
docs/EN/SCORING.md |
Recommendation scoring algorithm |
docs/EN/CAMELOT_WHEEL.md |
Camelot Wheel key compatibility reference |
docs/EN/INGESTION.md |
Ingestion pipeline guide |
docs/EN/TAILSCALE.md |
Remote access setup |