A cross-platform music information service that displays currently playing media as beautiful SVG cards. Perfect for OBS streaming overlays, GitHub profiles, and web integrations.
- Beautiful SVG Cards: Multiple customizable templates with high-quality album art
- Cross-Platform: Native support for Windows and macOS media APIs
- Real-time Updates: Live media information with automatic refresh
- OBS Integration: Perfect for streaming overlays and screen recordings
- GitHub Integration: Display your music taste on your profile
- Modern Stack: Built with FastAPI, uv, and platform-specific APIs
- Responsive Design: Adapts to different screen sizes and contexts
Pick a style by adding ?template=<name> to the card URL. The six designs below are the current lineup — expand the section underneath for the legacy set.
Show the 2 classic templates »
music-card · full-bleed portrait artwork
|
minimalist · transparent overlay
|
- Python 3.9 or higher
- uv package manager
# Clone the repository
git clone https://github.com/ACAne0320/now-playing.git
cd now-playing
# Copy configuration template
cp config.example.json config.json
# Edit config.json as needed (optional)
# Run the interactive launcher
chmod +x start.sh
./start.sh# Install uv if not already installed
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone and navigate to the project
git clone https://github.com/ACAne0320/now-playing.git
cd now-playing
# Setup configuration
cp config.example.json config.json
# Sync dependencies
uv sync
# Platform-specific dependencies
uv sync --extra windows # Windows
uv sync --extra macos # macOS
# Start the service (will use config.json settings)
uv run python -m client.mainPerfect for streaming and local applications:
# Configure config.json for local mode
{
"server": {
"public_mode": false,
"port": 8000
}
}
# Start local service (will use config.json settings)
uv run python -m client.mainAccess points:
- Health check:
http://localhost:8000/(or your configured port) - Web interface:
http://localhost:8000/web - SVG card:
http://localhost:8000/now-playing.svg - Status API:
http://localhost:8000/api/v1/status
OBS Studio Setup:
- Add "Browser Source" to your scene
- Set URL to:
http://localhost:8000/now-playing.svg(adjust port if needed) - Set dimensions to 400x120 (or customize)
- Enable "Refresh browser when scene becomes active"
Deploy to cloud for GitHub README integration:
# Configure config.json for public mode
{
"server": {
"public_mode": true,
"api_key": "your-secure-api-key",
"port": 8000
}
}
# Or use environment variables
export PUBLIC_MODE=true
export NOW_PLAYING_API_KEY=your-secure-api-key
# Start public server
uv run python -m client.mainOn a serverless host (e.g. Vercel) also configure a shared KV store so all template cards stay in sync — see Vercel deployment. A single long-lived server needs no KV.
GitHub README Integration:
Send local media data to remote server:
# Configure config.json
{
"server": {
"public_mode": false,
"api_key": "dev-secret-key-12345",
"poll_interval": 5,
"log_level": "INFO",
"port": 8000,
"template_dir": "",
"enable_album_art": true
},
"client": {
"server_url": "https://yours-now-playing-serverp",
"api_key": "your-secure-api-key",
"poll_interval": 10,
"template": "turntable"
}
}
# Start client
uv run python server/public_client.pyThe service uses a unified configuration system with the following priority:
- Environment Variables (highest priority)
- config.json (main configuration file)
- Built-in defaults (lowest priority)
# Copy example configuration
cp config.example.json config.json
# Edit configuration as needed
# The config.json file contains both server and client settings{
"server": {
"public_mode": false, // Enable public/private mode
"api_key": "your-secret-key", // API authentication key
"poll_interval": 5, // Media polling interval (seconds)
"log_level": "INFO", // Logging level
"port": 8000, // Server port
"template_dir": "", // Custom template directory
"enable_album_art": true // Enable album art display
},
"client": {
"server_url": "http://localhost:8000", // Remote server URL
"api_key": "your-secret-key", // Client API key
"poll_interval": 10, // Client polling interval
"template": "turntable" // Default template
}
}You can override any configuration setting using environment variables:
# Server configuration
export PUBLIC_MODE=true
export NOW_PLAYING_API_KEY=your-secret-key
export NOW_PLAYING_POLL_INTERVAL=5
export LOG_LEVEL=DEBUG
export PORT=8080
export TEMPLATE_DIR=./custom_templates
export ENABLE_ALBUM_ART=false
# Client configuration
export NOW_PLAYING_SERVER_URL=https://your-app.vercel.app
export NOW_PLAYING_CLIENT_API_KEY=your-client-key
export NOW_PLAYING_CLIENT_POLL_INTERVAL=15
export NOW_PLAYING_TEMPLATE=turntableAvailable templates:
turntable: Spinning vinyl deck with a tonearm that parks when pausedterminal: Phosphor-green CRT terminal with scanlines and a blinking cursorposter: Swiss brutalist typographic posterneon: Neon sign that switches to CLOSED when nothing is playingticket: Concert ticket stub with perforation, barcode and PAUSED stamppolaroid: Taped instant photo with handwritten captionmusic-card: Full-bleed portrait artwork cardminimalist: Transparent text-only overlay for OBS/streaming
# Use specific template
http://localhost:8000/now-playing.svg?template=music-cardInject custom styles:
# Custom colors
http://localhost:8000/now-playing.svg?custom_css=.title{fill:red;}.artist{fill:blue;}
# Custom fonts
http://localhost:8000/now-playing.svg?custom_css=.title{font-family:Arial,sans-serif;}| Platform | API | Status | Features |
|---|---|---|---|
| Windows 10/11 | Windows Media Control API | ✅ Full | Media info, album art, playback status |
| macOS 10.15+ | MediaRemote.framework + mediaremote-adapter | ✅ Full | Media info, album art, playback status |
# Build image
docker build -t now-playing .
# Run container
docker run -d -p 8000:8000 \
-e PUBLIC_MODE=true \
-e NOW_PLAYING_API_KEY=your-key \
now-playing-
Import the repo into Vercel (the included
vercel.jsonandapi/index.pyare detected automatically). -
Add a shared KV store so every serverless instance reports the same track (see below), then connect it to the project.
-
Set the project Environment Variables:
Variable Value PUBLIC_MODEtrueNOW_PLAYING_API_KEYa secret shared with your local client KV_REST_API_URLfrom your KV store (auto-added if you use Vercel KV) KV_REST_API_TOKENfrom your KV store (auto-added if you use Vercel KV) -
Deploy, then run the local client in Client Mode pointed at your Vercel URL.
Why the KV store? In public mode the "now playing" state is shared via Upstash / Vercel KV. Without it the state is kept only in each serverless function's memory, so an update that lands on one instance is invisible to the instance serving another template — causing some README cards to show your track while others show "No music playing". With KV configured, all templates stay in sync. Standalone Upstash works too (
UPSTASH_REDIS_REST_URL/UPSTASH_REDIS_REST_TOKEN). If no KV variables are set, the server falls back to in-memory storage (fine for a single long-lived server, not for serverless).
uv sync --dev- Create SVG template in
client/renderer/templates/ - Use Jinja2 templating with
media_infocontext - Follow existing template patterns for consistency
- Test with various media states
<!-- Example template structure -->
<svg width="400" height="120" xmlns="http://www.w3.org/2000/svg">
{% if media_info %}
<text class="title">{{ media_info.title }}</text>
<text class="artist">{{ media_info.artist }}</text>
{% if media_info.album_art_b64 %}
<image href="data:image/png;base64,{{ media_info.album_art_b64 }}" />
{% endif %}
{% else %}
<text>No media playing</text>
{% endif %}
</svg>- Authentication: API key-based authentication for public mode
- Rate Limiting: Built-in request throttling
- Input Validation: Pydantic-based data validation
- CORS: Configurable cross-origin resource sharing
- Use strong API keys (32+ characters)
- Enable HTTPS in production
- Regularly rotate API keys
- Monitor logs for unusual activity
- Implement IP whitelisting if needed
Special thanks to the following open-source projects that make this service possible:
- mediaremote-adapter by @ungive - Essential for accessing macOS MediaRemote framework on macOS 15.4+. This library provides the critical functionality needed to bypass Apple's MediaRemote restrictions and access complete media information including album artwork.
- FastAPI - Modern, fast web framework for building APIs with Python 3.6+
- uv - Ultra-fast Python package installer and resolver
This project is licensed under the MIT License - see the LICENSE file for details.