Table of contents
novatorem: Music-provider data into an SVG widget
This novatorem fork builds music-status widgets from provider data. A Flask orchestrator handles rendering, while Spotify and Last.fm have separate modules. Album artwork supplies both an image and colors for the SVG.
- Languages
- 5Code breakdown
- Documented components
- 2Explore modules
- Source documents
- 1Read the instructions
Repository languagesPython 81.61%5
Shares calculated from GitHub Linguist bytes.
| Language | % | Bytes |
|---|---|---|
| Python | 86,529 | |
| Jinja | 17,032 | |
| HTML | 1,741 | |
| Dockerfile | 677 | |
| Procfile | 47 |
1. Overview
1.1. Why it exists
Real-time dynamic GitHub profile README updater streaming live Spotify listening status.
1.2. Best fit
Live GitHub profile personalization and webhook integration.
2. Use cases
Flask service that renders a Spotify or Last.fm now-playing card as an SVG for a GitHub profile README.
- Show the now-playing card
- Profile ownerSpotify Web API
The catch-all route in api/orchestrator.py fetches the current track, extracts colours from the album art and returns an image/svg+xml card; vercel.json maps /api/spotify to it.
- Customize card colours
- Profile owner
Query parameters background_color, border_color, background_type, show_status and compact are validated in api/config.py before rendering the Jinja2 template chosen in api/templates.json.
- Use Last.fm as a source
- Profile ownerLast.fm API
When only LAST_FM_API_KEY and LAST_FM_USERNAME are set, or Spotify auth fails, get_now_playing switches to api/lastfm.py, which can pull album art from Deezer.
- Open the current song
- Profile ownerSpotify Web API
GET /redirect sends the visitor to the track URL, or to the upstream repository when nothing is playing.
- Renew the refresh token
- Profile ownerSpotify Web API
scripts/spotify_local_auth.py captures the OAuth code on http://127.0.0.1:8000/callback and writes a new refresh token, optionally pushing it to Vercel with --update-vercel.
- Preview the card locally
- Profile owner
python start.py creates .venv, installs requirements, starts Flask on port 5000 and opens /preview; docker-compose.yml runs the same app with a /health check.
2.1. Scenario: Add a Spotify card to a GitHub profile
- Create a Spotify app with the redirect URI http://127.0.0.1:8000/callback.
- Copy .env.example to .env and fill SPOTIFY_CLIENT_ID and SPOTIFY_SECRET_ID.
- Run python scripts/spotify_local_auth.py to obtain SPOTIFY_REFRESH_TOKEN.
- Run python start.py and check the card at http://127.0.0.1:5000/preview.
- Deploy to Vercel and embed <deployment>/api/spotify as an image linked to /redirect in the profile README.
Files readREADME.mdSetUp.md.env.exampleapi/orchestrator.pyapi/spotify.pyapi/lastfm.pyapi/config.pyapi/templates.jsonvercel.jsonstart.pydocker-compose.ymlscripts/spotify_local_auth.pyscripts/renew_spotify_token.py
3. Try it
Address published in the repository README. The page opens in a sandboxed frame, only when you ask.
https://novatorem.vercel.app4. Getting started
These commands come from the repository documentation. They were not run during this review.
Tasks declared in manifests
5. Repository references
The repository does not provide a detailed guide for these steps. Start with its README and the source walkthrough above.
6. Features and components 2
- Provider and presentation
- api/orchestrator.py owns the Flask application and rendering helpers. Provider modules sit alongside it. Configuration controls templates and palettes, keeping service-specific data access separate from SVG appearance.
- Artwork processing
- ImageData fetches artwork once per object and reuses the bytes for base64 output and ColorThief palettes. load_image_with_fallback tries artwork, then a placeholder, then default colors when image processing fails.
7. Architecture
Expand a component to follow its connections, or explore the full diagram.
Spotify or Last.fm
Provider data
Flask orchestrator
Artwork and palette fallback
SVG template response
Mermaid Interactive diagram
System overview of the components and connections verified in the repository.

flowchart LR
n0["Spotify or Last.fm"]
n1["Provider data"]
n2["Flask orchestrator"]
n3["Artwork and palette fallback"]
n4["SVG template response"]
n0 --> n1
n1 --> n2
n2 --> n3
n3 --> n4
Text version of the diagram
- Spotify or Last.fm → Provider data
- Provider data → Flask orchestrator
- Flask orchestrator → Artwork and palette fallback
- Artwork and palette fallback → SVG template response
Follow one path through the code
Follow ImageData._fetch into get_base64 and get_palette. Reusing bytes avoids repeated image downloads within that object; it is not a persistent cross-request cache.
8. Technical dossier
Built from the source at commit 5496fe0 (16 files). Each item comes from a pattern match in the code, so open the linked file before relying on it.
8.1. Layers and modules
Folders are sorted into layers by name, from the user interface down to data access. Because the sorting uses names only, the diagram says nothing about calls between layers.
Mermaid Layered view
flowchart TB
subgraph L_presentation["Presentation"]
direction TB
presentation_0["(root)<br/>1 file"]
end
subgraph L_interface["Entry points and API"]
direction TB
interface_0["api<br/>6 files"]
end
subgraph L_application["Application services"]
direction TB
application_0["scripts<br/>3 files"]
end
subgraph L_core["Core source"]
direction TB
core_0["(root)<br/>1 file"]
end
subgraph L_hosting["Build, CI and hosting"]
direction TB
hosting_0["(root)<br/>5 files"]
end
L_presentation ~~~ L_interface
L_interface ~~~ L_application
L_application ~~~ L_core
L_core ~~~ L_hosting
8.2. C4 model
From system context to code declarations. Each level reflects the available source evidence.
UML 1 · System context
The widest C4 view: who uses the project and which outside systems and data stores it relies on. Rendered with uml-mcp.
PlantUML source
@startuml
!include <C4/C4_Context>
LAYOUT_LEFT_RIGHT()
Person(P_http, "HTTP client")
Person(P_cli, "CLI user")
System(SYS, "novatorem", "This novatorem fork builds music-status widgets from provider data")
System_Ext(E0, "Spotify Web API")
Rel(P_http, SYS, "Uses")
Rel(P_cli, SYS, "Uses")
Rel(SYS, E0, "Calls")
@enduml
UML 2 · Containers
One level closer: the parts that run separately inside the project, their main language and how calls flow between them. Rendered with uml-mcp.
PlantUML source
@startuml
!include <C4/C4_Container>
LAYOUT_LEFT_RIGHT()
Person(P_http, "HTTP client")
Person(P_cli, "CLI user")
System_Boundary(SYS, "novatorem") {
Container(C0, "now-playing", "Docker")
}
System_Ext(E0, "Spotify Web API")
Rel(P_http, C0, "Uses")
Rel(P_cli, C0, "Uses")
Rel(C0, E0, "Calls")
@enduml
UML 3 · Components and layers
Modules grouped by source-layer classification. Boundaries show organization, not runtime isolation or call relationships. Rendered with uml-mcp.
PlantUML source
@startuml
!include <C4/C4_Component>
LAYOUT_TOP_DOWN()
System_Boundary(SYS, "novatorem") {
Boundary(L0, "Presentation", "layer") {
Component(M0_0, "(root)", "HTML", "1 files")
}
Boundary(L1, "Entry points and API", "layer") {
Component(M1_0, "api", "Python", "6 files")
}
Boundary(L2, "Application services", "layer") {
Component(M2_0, "scripts", "Python", "3 files")
}
Boundary(L3, "Core source", "layer") {
Component(M3_0, "(root)", "Python", "1 files")
}
Boundary(L4, "Build, CI and hosting", "layer") {
Component(M4_0, "(root)", ".json, Dockerfile, Procfile", "5 files")
}
}
@enduml
UML 4 · Code
Selected declarations, fields and methods grouped by source file. No inheritance or call edges are inferred. Rendered with uml-mcp.
PlantUML source
@startuml
skinparam backgroundColor transparent
skinparam shadowing false
skinparam roundCorner 10
skinparam defaultFontName Arial
skinparam defaultFontSize 13
skinparam ArrowColor #4e6070
skinparam ArrowFontColor #3d4f5f
skinparam titleFontSize 14
skinparam actorBorderColor #006a62
skinparam actorBackgroundColor #e9f3f4
skinparam usecaseBorderColor #006a62
skinparam usecaseBackgroundColor #e9f3f4
skinparam componentBorderColor #006a62
skinparam componentBackgroundColor #e9f3f4
skinparam interfaceBorderColor #006a62
skinparam interfaceBackgroundColor #ffffff
skinparam nodeBorderColor #4e6070
skinparam nodeBackgroundColor #f4f8fb
skinparam databaseBorderColor #006a62
skinparam databaseBackgroundColor #ffffff
skinparam cloudBorderColor #4e6070
skinparam cloudBackgroundColor #f0f5fa
skinparam artifactBorderColor #006a62
skinparam artifactBackgroundColor #ffffff
skinparam rectangleBorderColor #b0c2d3
skinparam activityBorderColor #006a62
skinparam activityBackgroundColor #e9f3f4
skinparam partitionBorderColor #b0c2d3
skinparam stereotypeCBackgroundColor #ffffff
top to bottom direction
hide empty members
package "novatorem" {
package "api/orchestrator.py" as F0 {
class "ImageData" as T0_0 {
_fetch()
get_base64()
get_palette()
bar_palette()
song_palette()
}
}
package "api/config.py" as F1 {
class "CompactSVGConfig" as T1_0 {
width
height
album_art_size
border_radius
widget_padding_top
widget_padding_right
widget_padding_bottom
widget_padding_left
}
class "SVGConfig" as T1_1 {
width
height
album_art_size
border_radius
widget_padding_top
widget_padding_right
widget_padding_bottom
widget_padding_left
}
class "SpotifyConfig" as T1_2 {
client_id
client_secret
refresh_token
token_url
now_playing_url
recently_played_url
}
class "LastFmConfig" as T1_3 {
api_key
username
api_url
deezer_search_url
placeholder_hash
}
class "TemplateConfig" as T1_4 {
config_path
fallback_theme
default_theme
}
}
package "api/spotify.py" as F2 {
class "TrackInfo" as T2_0 {
is_playing
track_name
artist_name
album_name
album_art_url
track_url
artist_url
track_id
}
class "AudioFeatures" as T2_1 {
tempo
energy
danceability
valence
loudness
}
}
package "api/lastfm.py" as F3 {
class "TrackInfo" as T3_0 {
is_playing
track_name
artist_name
album_name
album_art_url
track_url
artist_url
}
}
}
@enduml
8.3. Public interface
Outside code can use everything listed here. The project also declares 19 public types.
UML Use cases
Each actor is a kind of caller, linked to what it can ask the project to do. Rendered with uml-mcp.
PlantUML source
@startuml
skinparam backgroundColor transparent
skinparam shadowing false
skinparam roundCorner 10
skinparam defaultFontName Arial
skinparam defaultFontSize 13
skinparam ArrowColor #4e6070
skinparam ArrowFontColor #3d4f5f
skinparam titleFontSize 14
skinparam actorBorderColor #006a62
skinparam actorBackgroundColor #e9f3f4
skinparam usecaseBorderColor #006a62
skinparam usecaseBackgroundColor #e9f3f4
skinparam componentBorderColor #006a62
skinparam componentBackgroundColor #e9f3f4
skinparam interfaceBorderColor #006a62
skinparam interfaceBackgroundColor #ffffff
skinparam nodeBorderColor #4e6070
skinparam nodeBackgroundColor #f4f8fb
skinparam databaseBorderColor #006a62
skinparam databaseBackgroundColor #ffffff
skinparam cloudBorderColor #4e6070
skinparam cloudBackgroundColor #f0f5fa
skinparam artifactBorderColor #006a62
skinparam artifactBackgroundColor #ffffff
skinparam rectangleBorderColor #b0c2d3
skinparam activityBorderColor #006a62
skinparam activityBackgroundColor #e9f3f4
skinparam partitionBorderColor #b0c2d3
skinparam stereotypeCBackgroundColor #ffffff
left to right direction
actor "HTTP client" as A_http
actor "CLI user" as A_cli
rectangle "novatorem" {
usecase "ANY /" as U0
usecase "ANY /'path:path'" as U1
usecase "ANY /with_parameters" as U2
usecase "ANY /preview" as U3
usecase "ANY /redirect" as U4
usecase "ANY /health" as U5
usecase "Run the tool (12 options)" as U6
}
A_http --> U0
A_http --> U1
A_http --> U2
A_http --> U3
A_http --> U4
A_http --> U5
A_cli --> U6
@enduml
HTTP routes 6
Mermaid Route map by prefix
flowchart LR
api(["HTTP API"])
g0["/"]
api --> g0
g0r0["ANY /"]
g0 --> g0r0
g1["/'path:path'"]
api --> g1
g1r0["ANY /'path:path'"]
g1 --> g1r0
g2["/health"]
api --> g2
g2r0["ANY /health"]
g2 --> g2r0
g3["/preview"]
api --> g3
g3r0["ANY /preview"]
g3 --> g3r0
g4["/redirect"]
api --> g4
g4r0["ANY /redirect"]
g4 --> g4r0
g5["/with_parameters"]
api --> g5
g5r0["ANY /with_parameters"]
g5 --> g5r0
| Method | Path | Source |
|---|---|---|
| ANY | / | api/orchestrator.py:660 |
| ANY | /<path:path> | api/orchestrator.py:661 |
| ANY | /with_parameters | api/orchestrator.py:662 |
| ANY | /preview | api/orchestrator.py:693 |
| ANY | /redirect | api/orchestrator.py:710 |
| ANY | /health ops | api/orchestrator.py:729 |
Routes marked ops expose health, status or admin endpoints; restrict them in production.
Command line 12
8.4. Private interface
These names are internal by the language’s conventions: a leading underscore, private, static or pub(crate).
- underscore helper
- 15
_get_spotify_dataapi/lastfm.py:43_fetch_album_art_from_deezerapi/lastfm.py:112_api_getapi/lastfm.py:170
8.5. Services
Service 1
SpotifyTokenManager_get_auth_header, _refresh_token, get_token and 1 more
Handler 1
Handlerdo_GET, log_message
8.6. Entities and data model 8
Mermaid Entity class diagram
classDiagram
direction LR
class E0["CompactSVGConfig"] {
+int width
+int height
+int album_art_size
+int border_radius
+int widget_padding_top
+int widget_padding_right
}
class E1["SVGConfig"] {
+int width
+int height
+int album_art_size
+int border_radius
+int widget_padding_top
+int widget_padding_right
}
class E2["TrackInfo"] {
+bool is_playing
+str track_name
+str artist_name
+str album_name
+str album_art_url
+str track_url
}
class E3["SpotifyConfig"] {
+str client_id
+str client_secret
+str refresh_token
+str token_url
+str now_playing_url
+str recently_played_url
}
class E4["AudioFeatures"] {
+float tempo
+float energy
+float danceability
+float valence
+float loudness
}
class E5["LastFmConfig"] {
+str api_key
+str username
+str api_url
+str deezer_search_url
+str placeholder_hash
}
class E6["TemplateConfig"] {
+str config_path
+str fallback_theme
+str default_theme
}
An arrow links a field to the entity its type names.
| Entity | Kind | First fields |
|---|---|---|
CompactSVGConfig | Dataclass | width, height, album_art_size, border_radius, widget_padding_top, widget_padding_right, widget_padding_bottom, widget_padding_left |
SVGConfig | Dataclass | width, height, album_art_size, border_radius, widget_padding_top, widget_padding_right, widget_padding_bottom, widget_padding_left |
TrackInfo | Dataclass | is_playing, track_name, artist_name, album_name, album_art_url, track_url, artist_url, track_id |
TrackInfo | Dataclass | is_playing, track_name, artist_name, album_name, album_art_url, track_url, artist_url |
SpotifyConfig | Dataclass | client_id, client_secret, refresh_token, token_url, now_playing_url, recently_played_url |
AudioFeatures | Dataclass | tempo, energy, danceability, valence, loudness |
LastFmConfig | Dataclass | api_key, username, api_url, deezer_search_url, placeholder_hash |
TemplateConfig | Dataclass | config_path, fallback_theme, default_theme |
8.7. Business logic
Once UI code and the services and entities above are set aside, these classes have the most methods.
ImageDataEntry points and API, 5 methods_fetch, get_base64, get_palette, bar_palette, song_palette
8.8. External integrations
HTTP clients
Hosts named in the code
accounts.spotify.com3 filesapi/config.py:34api.spotify.com3 filesapi/config.py:35api.deezer.com1 filesapi/config.py:52open.spotify.com1 filesapi/lastfm.py:66picsum.photos1 filesapi/config.py:235ws.audioscrobbler.com1 filesapi/config.py:51www.last.fm1 filesapi/lastfm.py:313
8.9. Stack and dependencies
Python requirements.txt 13 runtime
External dependencies
13 declared packages (13 runtime, 0 development) in package.json, requirements.txt.
Package table 13
| Package | Version | Scope | Purpose | Registry |
|---|---|---|---|---|
Flask | >=3.0.0,<4.0.0 | runtime | Web framework | pypi.org |
Werkzeug | >=3.0.0,<4.0.0 | runtime | The comprehensive WSGI web application library. | pypi.org |
Jinja2 | >=3.1.0,<4.0.0 | runtime | Templates | pypi.org |
gunicorn | >=21.0.0,<23.0.0 | runtime | Application server | pypi.org |
requests | >=2.31.0,<3.0.0 | runtime | HTTP client | pypi.org |
urllib3 | >=2.0.0,<3.0.0 | runtime | HTTP client | pypi.org |
certifi | >=2023.0.0 | runtime | Python package for providing Mozilla's CA Bundle. | pypi.org |
python-dotenv | >=1.0.0,<2.0.0 | runtime | Configuration | pypi.org |
colorthief | >=0.2.1,<1.0.0 | runtime | A module for grabbing the color palette from an image. | pypi.org |
Pillow | >=10.0.0,<11.0.0 | runtime | Graphics, imaging and 3D | pypi.org |
click | >=8.1.0,<9.0.0 | runtime | Command line | pypi.org |
itsdangerous | >=2.1.0,<3.0.0 | runtime | Safely pass data to untrusted environments and back. | pypi.org |
MarkupSafe | >=2.1.0,<3.0.0 | runtime | Safely add untrusted strings to HTML/XML markup. | pypi.org |
8.10. CI/CD pipeline
No CI/CD pipeline in the repository. The code is built and run by hand, as described above.
8.11. Deployment
Mermaid Deployment diagram
flowchart LR
repo[("GitHub repository")]
image["Docker image<br/>FROM python:3.12-slim"]
host_Vercel("Vercel<br/>vercel.json")
subgraph compose["Docker Compose"]
svc_now_playing["now-playing"]
end
repo -->|"docker build"| image
repo -.->|"configured"| host_Vercel
image -->|"docker compose up"| compose
UML Deployment
The outer node is where the code runs. Each base image points to what it builds, solid arrows lead to databases and dashed ones to outside services. Rendered with uml-mcp.
PlantUML source
@startuml
skinparam backgroundColor transparent
skinparam shadowing false
skinparam roundCorner 10
skinparam defaultFontName Arial
skinparam defaultFontSize 13
skinparam ArrowColor #4e6070
skinparam ArrowFontColor #3d4f5f
skinparam titleFontSize 14
skinparam actorBorderColor #006a62
skinparam actorBackgroundColor #e9f3f4
skinparam usecaseBorderColor #006a62
skinparam usecaseBackgroundColor #e9f3f4
skinparam componentBorderColor #006a62
skinparam componentBackgroundColor #e9f3f4
skinparam interfaceBorderColor #006a62
skinparam interfaceBackgroundColor #ffffff
skinparam nodeBorderColor #4e6070
skinparam nodeBackgroundColor #f4f8fb
skinparam databaseBorderColor #006a62
skinparam databaseBackgroundColor #ffffff
skinparam cloudBorderColor #4e6070
skinparam cloudBackgroundColor #f0f5fa
skinparam artifactBorderColor #006a62
skinparam artifactBackgroundColor #ffffff
skinparam rectangleBorderColor #b0c2d3
skinparam activityBorderColor #006a62
skinparam activityBackgroundColor #e9f3f4
skinparam partitionBorderColor #b0c2d3
skinparam stereotypeCBackgroundColor #ffffff
node "Docker Compose" as H {
node "now-playing" as N0 <<container>>
}
artifact "FROM python:3.12-slim" as IMG0
IMG0 ..> N0 : builds
cloud "Vercel" as X0
X0 ..> N0
cloud "Spotify Web API" as E0
N0 ..> E0
@enduml
Where it runs
Compose services
now-playing
Docker base images
8.12. Monitoring
What the code logs, measures and exposes to tell whether it is working.
Health and metrics routes
Container health checks
8.13. Security
Controls
Supply chain
Environment variables 7
The page lists names without their values and underlines the ones that look like secrets.
SPOTIFY_SECRET_ID LAST_FM_API_KEY SPOTIFY_REFRESH_TOKEN PORT SPOTIFY_CLIENT_ID LAST_FM_USERNAME SPOTIFY_REDIRECT_URI
Nothing found for: Database, Testing.
9. Types and file structure
19 declared types, with 12 in the base-relationship overview.
Type diagram
classDiagram
direction TB
class t7697cb5ba057["TrackInfo"] {
}
class tce9a5c3a3832["ImageData"] {
__init__()
_fetch()
get_base64()
get_palette()
}
class t576319e97c4e["TrackInfo"] {
}
class tc5e631da8d1c["AudioFeatures"] {
beat_duration_ms()
to_dict()
}
class t4be5079b8ec0["SpotifyTokenManager"] {
__init__()
_get_auth_header()
_refresh_token()
get_token()
}
class t58ad657833a8["SpotifyConfig"] {
is_configured()
}
class tcb0edfe91d38["LastFmConfig"] {
is_configured()
}
class t3c92c85d61ad["SVGConfig"] {
}
class te1c73c384f8d["CompactSVGConfig"] {
}
class t9f095ad5e068["TemplateConfig"] {
}
class t97dfb1fa9ef2["MusicWidgetError"] {
__init__()
}
class t7be6e7ad4bde["ConfigurationError"] {
__init__()
}
class b8564c6f24b53["Exception"] {
<<unresolved_base>>
}
b8564c6f24b53 <|-- t97dfb1fa9ef2
t97dfb1fa9ef2 <|-- t7be6e7ad4bde
Unresolved bases remain labelled; full signatures are not reconstructed.
10. Reference
14 / 14 eligible files read (29 in the tree); 0 oversized, 0 failed reads and 0 syntax diagnostics.
What the scan covers
Known dependency/build folders, credential files and binaries are excluded. The scan covers READMEs, recognized manifests and parser-supported source files.
Linguist statistics cover the whole repository and may include other languages.
12. Files to read 3
13. Glossary
Repository
The versioned source files, configuration, and history of a project.
README
The repository’s entry document, usually covering its purpose and initial setup.
Dependency
A library, tool, or service that another component needs to build or run.
API
An interface through which software components exchange requests and results. An API does not have to use HTTP.
Container
An isolated process environment created from an image. Persistent data needs storage outside the container’s disposable filesystem.
Volume and bind mount
A volume is storage managed by the container platform; a bind mount exposes a chosen host path inside a container.
Published port
A host port mapped to a service’s container port. Publishing a port can expose that service beyond its container network.
Build
The steps that turn source files and dependencies into a runnable program or distributable artifact.
GPU
A processor designed for parallel workloads, including graphics rendering and many numerical operations.
Fork
A separate repository derived from another project. A change in a fork is not automatically a change in the upstream project.
14. References
- antoinebou12. (2026, November 5). novatorem. [Software repository] github.com.
- antoinebou12. (2026, November 5). novatorem: README. [Repository documentation] github.com.
- antoinebou12. (2026, November 5). api/orchestrator.py. [Source file] github.com.
- antoinebou12. (2026, November 5). api/spotify.py. [Source file] github.com.
- antoinebou12. (2026, November 5). api/lastfm.py. [Source file] github.com.
- Boucher, A. (2026, October 23). novatorem: Music-provider data into an SVG widget. [Project article] antoineboucher.info.
Sources without a publication date show the date they were last checked, November 5, 2026. File links retain the inspected revision where available.