Skip to content
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.

Application and services Fork
  • Updated
  • 1
  • Python · 81.61%
  • Unlicense
Node.js Python Upstream credited
Documented components
2Explore modules
Source documents
1Read the instructions
Repository languagesPython 81.61%5

Shares calculated from GitHub Linguist bytes.

Python81.61%
81.61%86,529 bytes
Jinja16.06%
16.06%17,032 bytes
HTML1.64%
1.64%1,741 bytes
Dockerfile0.64%
0.64%677 bytes
Language%Bytes
Python81.61%86,529
Jinja16.06%17,032
HTML1.64%1,741
Dockerfile0.64%677
Procfile0.04%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.

Use-case diagram for novatorem: Show the now-playing card; Customize card colours; Use Last.fm as a source; Open the current song; Renew the refresh token; Preview the card locallynovatoremShow the now-playing cardCustomize card coloursUse Last.fm as a sourceOpen the current songRenew the refresh tokenPreview the card locallyProfile owner«system»Spotify Web API«system»Last.fm API
People on the left, external systems on the right; each oval is a use case backed by the files cited below.
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

  1. Create a Spotify app with the redirect URI http://127.0.0.1:8000/callback.
  2. Copy .env.example to .env and fill SPOTIFY_CLIENT_ID and SPOTIFY_SECRET_ID.
  3. Run python scripts/spotify_local_auth.py to obtain SPOTIFY_REFRESH_TOKEN.
  4. Run python start.py and check the card at http://127.0.0.1:5000/preview.
  5. 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.

Open in a new tab https://novatorem.vercel.app

4. 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.

Source documentation

1 documents read from 1 candidates.

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.

System components Context
Mermaid Interactive diagram

System overview of the components and connections verified in the repository.

Static architecture view for novatorem
Static preview. The interactive diagram replaces it after Mermaid loads.
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
Modules and their main layer
ModuleLayerFilesTypesExtensions
apiEntry points and API618.py
scriptsApplication services31.py
(root)Presentation20.html .py

8.2. C4 model

From system context to code declarations. Each level reflects the available source evidence.

UML 1 · System context
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
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
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
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
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

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

8.5. Services

Handler 1

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.

EntityKindFirst fields
CompactSVGConfigDataclasswidth, height, album_art_size, border_radius, widget_padding_top, widget_padding_right, widget_padding_bottom, widget_padding_left
SVGConfigDataclasswidth, height, album_art_size, border_radius, widget_padding_top, widget_padding_right, widget_padding_bottom, widget_padding_left
TrackInfoDataclassis_playing, track_name, artist_name, album_name, album_art_url, track_url, artist_url, track_id
TrackInfoDataclassis_playing, track_name, artist_name, album_name, album_art_url, track_url, artist_url
SpotifyConfigDataclassclient_id, client_secret, refresh_token, token_url, now_playing_url, recently_played_url
AudioFeaturesDataclasstempo, energy, danceability, valence, loudness
LastFmConfigDataclassapi_key, username, api_url, deezer_search_url, placeholder_hash
TemplateConfigDataclassconfig_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.

ImageData Entry points and API, 5 methods
_fetch, get_base64, get_palette, bar_palette, song_palette

8.8. External integrations

HTTP clients

requests urllib

Hosts named in the code

8.9. Stack and dependencies

Node.js package.json

node 24.x

Open the manifest

Python requirements.txt 13 runtime

Open the manifest

External dependencies

13 declared packages (13 runtime, 0 development) in package.json, requirements.txt.

Package table 13
External dependencies
PackageVersionScopePurposeRegistry
Flask>=3.0.0,<4.0.0runtimeWeb frameworkpypi.org
Werkzeug>=3.0.0,<4.0.0runtimeThe comprehensive WSGI web application library.pypi.org
Jinja2>=3.1.0,<4.0.0runtimeTemplatespypi.org
gunicorn>=21.0.0,<23.0.0runtimeApplication serverpypi.org
requests>=2.31.0,<3.0.0runtimeHTTP clientpypi.org
urllib3>=2.0.0,<3.0.0runtimeHTTP clientpypi.org
certifi>=2023.0.0runtimePython package for providing Mozilla's CA Bundle.pypi.org
python-dotenv>=1.0.0,<2.0.0runtimeConfigurationpypi.org
colorthief>=0.2.1,<1.0.0runtimeA module for grabbing the color palette from an image.pypi.org
Pillow>=10.0.0,<11.0.0runtimeGraphics, imaging and 3Dpypi.org
click>=8.1.0,<9.0.0runtimeCommand linepypi.org
itsdangerous>=2.1.0,<3.0.0runtimeSafely pass data to untrusted environments and back.pypi.org
MarkupSafe>=2.1.0,<3.0.0runtimeSafely 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
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

python:3.12-slim

8.12. Monitoring

What the code logs, measures and exposes to tell whether it is working.

Health and metrics routes

ANY /health

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

  1. antoinebou12. (2026, November 5). novatorem. [Software repository] github.com.
  2. antoinebou12. (2026, November 5). novatorem: README. [Repository documentation] github.com.
  3. antoinebou12. (2026, November 5). api/orchestrator.py. [Source file] github.com.
  4. antoinebou12. (2026, November 5). api/spotify.py. [Source file] github.com.
  5. antoinebou12. (2026, November 5). api/lastfm.py. [Source file] github.com.
  6. 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.