๐ต lastfm-collage-generator
Production-grade Python library for generating high-resolution visual composite image collages from Last.fm scrobble histories.
Key Features โข Grid Geometry โข Architecture โข Installation โข Quickstart โข API Reference โข Developer Workflows โข Testing & QA โข Caching & Social Presets โข GitHub Actions โข Roadmap โข Defect Catalog โข Contributing
โจ Key Features
- Multi-Entity Composite Grids:
- ๐ฟ Top Albums (
album): Queries user scrobble history via the Last.fm Audioscrobbler REST API (pylast), downloads album cover artwork, and composites them into an aligned matrix. - ๐ค Top Artists (
artist): Fetches top artists via the API and scrapes high-resolution artist hero imagery directly fromhttps://www.last.fm/music/<artist>(bypassing the historical deprecation of artist images in the Last.fm API), thumbnails images to 300x300px, and composites the grid. - ๐ต Top Tracks (
track): Queries top tracks, resolves associated album artwork or applies solid black fallbacks, and renders the composite.
- ๐ฟ Top Albums (
- Configurable Grid Geometry: Generates rectangular or square grids from
1x1up to5x5(e.g.3x3standard 9-tile grid,5x525-tile poster, or asymmetric3x5). Standard tile resolution is300 x 300pixels. - Comprehensive Time Horizons: Full support for all Last.fm aggregation periods:
7day(Weekly),1month(Monthly),3month(Quarterly),6month(Semi-Annual),12month(Yearly), andoverall(All-Time). - Translucent Monospace Typography Banners: Renders a dark translucent banner overlay (
RGBA(0, 0, 0, 123)) on the bottom 65px of each tile displaying entity name, artist, and scrobble playcount in bundled TrueType monospace font (DejaVuSansMono.ttf). - Concurrent Image Acquisition: Utilizes Python's
concurrent.futures.ThreadPoolExecutorfor parallel, non-blocking downloads of artwork assets across worker threads. - Resilient Fallback Handling: Gracefully handles missing artwork, HTTP 404s, or network timeouts by rendering solid black
300x300blank tiles without failing the entire collage generation process. - Zero External System Dependencies: TrueType fonts are bundled directly in the distribution package and loaded dynamically relative to module path.
- Modern Tooling & Developer Experience: Built with uv and
hatchling, featuring offline synthetic mock debugging runners, VS Code F5 launch profiles, and 100% offline pytest fixtures.
๐ Grid Dimensions & Geometry Reference
The library supports arbitrary square and rectangular grids from $1 \times 1$ up to $20 \times 20$ (up to 400 tiles), with dynamic resolution auto-scaling and configurable tile sizes:
| Grid Size | Total Tiles | Default Tile Resolution | Canvas Dimensions | Aspect Ratio | Megapixels | Typical Use Case |
|---|---|---|---|---|---|---|
1x1 |
1 tile | 300 x 300 px | 300 x 300 px | 1:1 Square | 0.09 MP | Single item avatar / badge |
3x3 |
9 tiles | 300 x 300 px | 900 x 900 px | 1:1 Square | 0.81 MP | Standard social media card (#LastFmFriday, Instagram) |
5x5 |
25 tiles | 300 x 300 px | 1500 x 1500 px | 1:1 Square | 2.25 MP | Classic weekly / monthly listening card |
8x8 |
64 tiles | 150 x 150 px (Auto) | 1200 x 1200 px | 1:1 Square | 1.44 MP | Detailed monthly recap poster |
10x10 |
100 tiles | 150 x 150 px (Auto) | 1500 x 1500 px | 1:1 Square | 2.25 MP | High-density 100-album annual overview |
20x20 |
400 tiles | 100 x 100 px (Auto) | 2000 x 2000 px | 1:1 Square | 4.00 MP | Comprehensive mega-recap poster |
3x10 |
30 tiles | 150 x 150 px (Auto) | 450 x 1500 px | 3:10 Portrait | 0.68 MP | Mobile sidebar / Story banner |
12x6 |
72 tiles | 100 x 100 px (Auto) | 1200 x 600 px | 2:1 Landscape | 0.72 MP | Ultra-wide desktop banner |
Dynamic Resolution Auto-Scaling Tiers
To keep memory footprint safe and execution fast without sacrificing visual detail:
- Standard Density ($\le 5 \times 5$): Defaults to 300x300 px per tile.
- Medium Density ($6 \times 6$ to $10 \times 10$): Automatically scales to 150x150 px per tile.
- High Density ($> 10 \times 10$, up to $20 \times 20$): Automatically scales to 100x100 px per tile.
- Custom Resolution: Explicitly supply
tile_size(between 50 and 600 px) to override automatic scaling.
Proportional Tile Layout Anatomy
Overlay typography and dark translucent banners automatically scale proportionally with tile size:
(x, y) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ Tile Cover Artwork โ
โ (Downloaded or Scraped) โ
โ (S x S px, e.g. 300px) โ
โ โ
y+h_0 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โโโ Banner Top: y + (S - h_banner)
โ Translucent Dark Banner: RGBA(0, 0, 0, 123) โ
y+y_t โ Monospace Typography: DejaVuSansMono.ttf (Proportional Font) โ โโโ Text Origin: x + pad_x, y_0 + pad_y
โ "Artist Name - Album / Track Title. (42 scrobbles)" โ
(x+S, โ โ
y+S) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโ Tile Bottom (y + S)
๐๏ธ System Architecture & Design Patterns
The library strictly implements a 4-layer object-oriented design: Facade โ Factory โ Builder โ Client Adapter.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Client / Consumer Application โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Facade Layer: CollageGenerator (src/collage_generator.py) โ
โ - Encapsulates LastfmConfig (API key and API secret) โ
โ - Validates grid bounds (1 <= cols/rows <= 5), entity, and period โ
โ - Dispatches generation request to CollageBuilderFactory โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. Factory Layer: CollageBuilderFactory (src/collage.py) โ
โ - Inspects entity string ("album", "artist", "track") โ
โ - Instantiates and returns the corresponding concrete Builder โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ
โ AlbumCollageBuilder โ โ ArtistCollageBuilder โ โ TrackCollageBuilder โ
โ - Queries pylast โ โ - Queries pylast โ โ - Queries pylast โ
โ - Downloads cover artโ โ - Scrapes Last.fm DOMโ โ - Resolves album art โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Inherits
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 3. Builder Layer: BaseCollageBuilder (src/collage.py) โ
โ - Template Method create(username): โ
โ 1. _get_tiles_from_top_items() -> Parallel ThreadPoolExecutor โ
โ 2. _create_image() -> Pillow RGB canvas allocation & tile pasting โ
โ 3. _insert_tile_title() -> Translucent RGBA banner overlay โ
โ - Font loading (DejaVuSansMono.ttf) & text wrapping โ
โ - Solid black (300x300) fallback tile generator โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 4. Client Adapter Layer: LastfmClient (src/lastfm/client.py) โ
โ - Wraps pylast.LastFMNetwork, isolating API network credentials โ
โ - Exposes get_user(), get_top_albums(), get_top_artists(), โ
โ and get_top_tracks() โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Module Responsibilities Matrix
| Module | Component | Pattern / Architectural Role | Key Responsibilities |
|---|---|---|---|
collage_generator.py |
CollageGenerator |
Facade | Public API entrypoint; credential management; input parameter boundary validation. |
collage.py |
CollageBuilderFactory |
Factory | Dispatches concrete builder instances based on entity string key. |
collage.py |
BaseCollageBuilder |
Base Builder / Template Method | Canvas lifecycle; concurrent worker pool; coordinate math; text wrapping; banner overlays. |
collage.py |
AlbumCollageBuilder |
Concrete Builder | Fetches top albums via pylast; downloads cover images from Last.fm CDN. |
collage.py |
ArtistCollageBuilder |
Concrete Builder | Scrapes last.fm/music/<artist> DOM for .header-new-background-image; thumbnails to 300x300px. |
collage.py |
TrackCollageBuilder |
Concrete Builder | Fetches top tracks; inherits album cover extraction and fallback mechanics. |
lastfm/client.py |
LastfmClient |
Client Adapter | Wraps pylast.LastFMNetwork calls; isolates third-party API types from core builders. |
constants.py |
ENTITIES, PERIODS |
Domain Constants | Defines supported entity tuples and Last.fm aggregation horizons. |
exceptions.py |
ArtistNotFound, etc. |
Exception Hierarchy | Custom domain errors for missing artists or unresolvable artwork. |
๐ฆ Installation
Using uv (Recommended)
uv add lastfmcollagegenerator
Using pip
pip install lastfmcollagegenerator
Using pipx (Standalone execution)
pipx install lastfmcollagegenerator
Runtime Requirements
- Python:
>= 3.8(Fully tested and verified on Python 3.8, 3.9, 3.10, 3.11, and 3.12). - Core Dependencies:
Pillow >= 10.4.0,pylast >= 5.3.0,requests >= 2.32.3,beautifulsoup4 >= 4.12.3,html5lib >= 1.1. - Last.fm API Account: Free API Key & Secret obtained from the Last.fm API Account Creation Page.
๐ Quickstart
Create and save a high-resolution 3x3 album collage in just a few lines of Python:
from lastfmcollagegenerator.collage_generator import CollageGenerator
# 1. Initialize the generator with your Last.fm API credentials
generator = CollageGenerator(
lastfm_api_key="YOUR_LASTFM_API_KEY",
lastfm_api_secret="YOUR_LASTFM_API_SECRET"
)
# 2. Generate a 3x3 album collage for the past 7 days (returns a PIL.Image object)
image = generator.generate(
entity="album",
username="your_lastfm_username",
cols=3,
rows=3,
period="7day",
)
# 3. Save the resulting composite image to disk
image.save("my_weekly_collage.png", format="PNG")
print(f"Collage saved successfully! Canvas size: {image.width}x{image.height}px")
# 4. Or generate directly using dedicated convenience methods
album_collage = generator.generate_top_albums_collage(
username="your_lastfm_username", cols=5, rows=5, period="7day"
)
artist_collage = generator.generate_top_artists_collage(
username="your_lastfm_username", cols=3, rows=3, period="overall"
)
track_collage = generator.generate_top_tracks_collage(
username="your_lastfm_username", cols=4, rows=4, period="1month"
)
๐ Python API Reference
CollageGenerator
lastfmcollagegenerator.collage_generator.CollageGenerator
The primary public entrypoint and facade for configuring and executing collage creation.
Constructor
CollageGenerator(lastfm_api_key: str, lastfm_api_secret: str)
Parameters:
lastfm_api_key(str): Valid Last.fm Audioscrobbler REST API key.lastfm_api_secret(str): Valid Last.fm API secret.
Method: generate()
def generate(
self,
entity: str,
username: str,
cols: int,
rows: int,
period: str = "overall",
tile_size: Optional[int] = None,
theme: Union[str, Theme, Dict[str, Any]] = "dark",
overlay_style: str = "banner",
show_text: bool = True,
font_path: Optional[str] = None
) -> PIL.Image.Image
Generates a composite image collage for the specified entity and time horizon.
Parameter Matrix:
| Parameter | Type | Allowed Values | Default | Description |
|---|---|---|---|---|
entity |
str |
"album", "artist", "track" |
Required | Type of Last.fm listening entity to composite. |
username |
str |
Any valid Last.fm username string | Required | The target Last.fm user account. |
cols |
int |
1 <= cols <= 20 |
Required | Number of horizontal grid columns. |
rows |
int |
1 <= rows <= 20 |
Required | Number of vertical grid rows (max 400 total tiles). |
period |
str |
"7day", "1month", "3month", "6month", "12month", "overall" |
"overall" |
Scrobble aggregation time window. |
tile_size |
Optional[int] |
50 <= tile_size <= 600 |
None (Auto) |
Explicit tile dimension in px. If None, dynamically auto-scaled. |
theme |
Union[str, Theme, dict] |
"dark", "light", "glassmorphic", "sunset", "neon", or custom Theme |
"dark" |
Visual color scheme for overlay banners and typography. |
overlay_style |
str |
"banner", "full_tint", "gradient", "pill", "clean" |
"banner" |
Overlay visual presentation layout mode. |
show_text |
bool |
True, False |
True |
Whether to render title and playcount overlays. |
font_path |
Optional[str] |
Filesystem path to .ttf/.otf |
None |
Custom font file path override. |
Returns:
PIL.Image.Image: An allocated Pillow 24-bit RGB raster canvas with dimensions(cols * tile_size, rows * tile_size)pixels.
Exceptions Raised:
ValueError: Ifentityis not inENTITIES,periodis not inPERIODS,cols/rowsare outside1..20, total tiles exceed 400,tile_sizeis outside50..600, ortheme/overlay_styleare invalid.TypeError: Ifcols,rows, ortile_sizeare not integers, or parameter types are invalid.FileNotFoundError: If customfont_pathdoes not exist on disk.pylast.WSError: If Last.fm API authentication fails or the requested username does not exist.pylast.NetworkError: If network connection to Last.fm API endpoints cannot be established.
Convenience Methods
For intuitive syntax, CollageGenerator provides dedicated helper methods:
# Generate Top Albums Collage (3x3, last 7 days)
album_collage = generator.generate_top_albums_collage(
username="rj",
cols=3,
rows=3,
period="7day"
)
# Generate High-Density Top Artists Collage (10x10, monthly, auto 150px tiles)
artist_collage = generator.generate_top_artists_collage(
username="rj",
cols=10,
rows=10,
period="1month"
)
# Generate Custom High-Res Top Tracks Collage (4x3, overall history, explicit 300px tiles)
track_collage = generator.generate_top_tracks_collage(
username="rj",
cols=4,
rows=3,
period="overall",
tile_size=300
)
Working with Returned PIL.Image Objects
The returned object is a standard Pillow PIL.Image.Image instance, offering full flexibility for saving, converting, streaming, or displaying:
import io
from lastfmcollagegenerator.collage_generator import CollageGenerator
generator = CollageGenerator("YOUR_API_KEY", "YOUR_API_SECRET")
image = generator.generate(entity="album", username="user", cols=3, rows=3, period="7day")
# 1. Save as Lossless PNG
image.save("collage.png", format="PNG")
# 2. Save as Optimized JPEG with custom compression quality
image.convert("RGB").save("collage.jpg", format="JPEG", quality=90, optimize=True)
# 3. Save as Modern WebP (reduces size by 60-80% relative to PNG)
image.save("collage.webp", format="WEBP", quality=85)
# 4. Extract In-Memory Binary Buffer (for FastAPI / Flask / Discord Bot responses)
buffer = io.BytesIO()
image.save(buffer, format="PNG")
buffer.seek(0)
raw_bytes = buffer.getvalue()
# 5. Generate a Thumbnail / Downscaled Version
thumbnail = image.copy()
thumbnail.thumbnail((450, 450))
thumbnail.save("collage_thumbnail.png")
# 6. Display inline within Jupyter / IPython Notebooks
display(image)
Error Handling & Exception Management
from lastfmcollagegenerator.collage_generator import CollageGenerator
from lastfmcollagegenerator.exceptions import ArtistNotFound, ArtistImageNotFound
import pylast
generator = CollageGenerator(
lastfm_api_key="your_api_key",
lastfm_api_secret="your_api_secret"
)
try:
image = generator.generate(
entity="album",
username="some_lastfm_user",
cols=3,
rows=3,
period="7day"
)
image.save("collage.png")
except ValueError as e:
print(f"Validation error (invalid parameters): {e}")
except pylast.WSError as e:
print(f"Last.fm API Service error: {e}")
except pylast.NetworkError as e:
print(f"Network connectivity error: {e}")
except (ArtistNotFound, ArtistImageNotFound) as e:
print(f"Artist scraping warning: {e}")
except Exception as e:
print(f"Unexpected error during collage generation: {e}")
๐ ๏ธ Developer & Debugging Workflows
The repository includes a comprehensive zero-build development runner (scripts/debug_collage.py) and preconfigured debug profiles.
1. Offline Mock Mode (0 Network Calls / Instant Rendering)
Generates synthetic in-memory colored tiles with geometric artwork to test Pillow canvas allocation, font rendering, coordinate math, and text wrapping instantly without API keys or an internet connection:
# Generate 3x3 mock album collage
uv run python scripts/debug_collage.py --mock -g 3x3
# Generate 5x5 mock artist collage and automatically open in system viewer
uv run python scripts/debug_collage.py --mock -e artist -g 5x5 --open
# Generate asymmetric 3x5 track collage without playcount banners
uv run python scripts/debug_collage.py --mock -e track -c 3 -r 5 --no-title -o output/mock_track.png
2. Live Last.fm Mode with .env Configuration
Configure credentials once in a .env file:
cp .env.example .env
# .env
LASTFM_API_KEY=your_api_key_here
LASTFM_API_SECRET=your_api_secret_here
LASTFM_USERNAME=your_username_here
Execute live queries:
# Run live generation using .env credentials and username
uv run python scripts/debug_collage.py --live
# Customize entity, period, grid size, and open viewer
uv run python scripts/debug_collage.py --live -e artist -g 4x4 -p 1month --open
# Override username or period on the fly
uv run python scripts/debug_collage.py --live -u different_user -e track -p overall
3. Debug Runner CLI Options Reference
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--mock |
flag |
False |
Run offline with synthetic colored tiles (0 network calls). | |
--live |
flag |
False |
Run live Last.fm API queries and web scraping. | |
--username |
-u |
str |
.env / testuser |
Target Last.fm username. |
--entity |
-e |
str |
album |
Musical entity: album, artist, track. |
--grid |
-g |
str |
3x3 |
Shorthand grid dimension (e.g. 3x3, 5x5, 4x3, 3x5). |
--cols / --rows |
-c / -r |
int |
3 / 3 |
Explicit column and row counts (1 to 5). |
--period |
-p |
str |
7day |
Aggregation period (7day, 1month, 3month, 6month, 12month, overall). |
--output |
-o |
str |
output/... |
Custom destination PNG filepath. |
--open |
flag |
False |
Automatically open result in default operating system image viewer. | |
--no-title |
flag |
False |
Disable title and playcount overlay banners. | |
--api-key |
str |
.env / "" |
Last.fm API Key override. | |
--api-secret |
str |
.env / "" |
Last.fm API Secret override. |
4. VS Code F5 Breakpoint Debugging
The repository includes preconfigured .vscode/launch.json debug profiles:
๐จ Debug: Mock Album Collage (3x3): Step through Pillow rendering offline.๐จ Debug: Mock Artist Collage (5x5): Step through 5x5 multi-row rendering.๐ Debug: Live Album Collage (.env): Step through live Last.fm API calls.๐ Debug: Live Artist Collage (.env): Step through web scraping pipeline.๐ต Debug: Live Track Collage (.env): Step through track cover fallbacks.๐งช Debug: Current Test File (Pytest): Step through active test cases.
5. Local Editable Installation for Consumer Applications
If you are developing a consuming application (e.g. a Discord bot or web service) alongside this library:
# In your consumer application's virtual environment:
uv pip install -e /path/to/lastfm-collage-generator
# or
pip install -e /path/to/lastfm-collage-generator
Any edits made inside src/ are immediately active in the consumer application without reinstalling.
๐งช Testing & Quality Assurance
The project enforces a zero live network calls policy for automated tests. All tests execute 100% offline using synthetic in-memory fixtures.
Running the Pytest Suite
# Run all unit and integration tests
uv run pytest tests/ -v
# Run with line and branch coverage report
uv run pytest --cov=lastfmcollagegenerator --cov-report=term-missing tests/
# Enforce minimum 90% coverage quality gate
uv run pytest --cov=lastfmcollagegenerator --cov-fail-under=90 tests/
Static Analysis & Linters
# Lint code syntax and PEP 8 compliance
uv run flake8 src/ tests/
# Verify Black code formatting
uv run black --check src/ tests/
# Run static type checking
uv run mypy src/
Unified QA Runner Skill
Execute the entire QA pipeline with a single command:
uv run python .gemini/skills/poetry-test-runner/scripts/run_tests.py --all
๐ฆ Packaging & Publishing to PyPI
The package is built and published using uv with the hatchling build backend.
1. Build Distribution Artifacts
Always ensure only clean, current release artifacts exist in dist/ prior to building:
# Clean previous build artifacts
rm -rf dist/
# Build source distribution (.tar.gz) and binary wheel (.whl)
uv build
2. Configure PyPI Authentication
uv publish authenticates automatically using the UV_PUBLISH_TOKEN environment variable or standard ~/.pypirc / ~/.netrc config:
- Shell profile (
~/.zshrc):export UV_PUBLISH_TOKEN="pypi-AgEI..."
- Project
.env:UV_PUBLISH_TOKEN=pypi-AgEI...
- Global
~/.pypirc:[distutils] index-servers = pypi [pypi] username = __token__ password = pypi-AgEI...
3. Publish to PyPI
# Publish built release to PyPI
uv publish
๐๏ธ Caching, Social Presets & Tile Geometry
Multi-Tier Artwork Caching
Artwork is cached across two tiers: an in-memory LRU store (256 entries) and a persistent SQLite cache at ~/.cache/lastfm-collage/artwork.db. Album/track covers expire after 30 days, scraped artist hero images after 7 days. On every cache hit, no HTTP request is issued.
generator.generate(
entity="album",
username="your_username",
cols=5,
rows=5,
cache_dir="/custom/cache/dir", # Default: ~/.cache/lastfm-collage/
cache_ttl_override=14, # Override TTL (days) for all artwork kinds
rate_limit=10, # HTTP requests per second (default 5.0)
)
All HTTP acquisition flows through a resilience middleware: a token-bucket rate limiter, exponential backoff with full jitter on transient errors, and a per-host circuit breaker. Unavailable artwork falls back to a deterministic algorithmic gradient tile (SHA-256-derived pastel gradient + initials) instead of a black square; pass fallback_style="black" for the legacy solid tile.
Social Media Dimension Presets
One-click output sizing for common platforms. When a preset is set it overrides cols, rows and tile_size, and letterboxed regions are filled with an acrylic backdrop (Gaussian-blurred, darkened rendition of your #1 artwork):
generator.generate(
entity="album",
username="your_username",
cols=5,
rows=5,
preset="instagram-story",
)
| Preset | Dimensions | Grid |
|---|---|---|
instagram-story |
1080 ร 1920 (9:16) | 3ร5 @ 360px |
instagram-post |
1080 ร 1080 (1:1) | 3ร3 @ 360px |
twitter-header |
1500 ร 500 (3:1) | 5ร1 @ 300px |
desktop-wallpaper |
1920 ร 1080 (16:9) | 6ร3 @ 320px |
desktop-wallpaper-4k |
3840 ร 2160 (16:9) | 6ร3 @ 600px |
Tile Geometry
Rounded corners, border strokes and inter-tile spacing are available with defaults preserving the classic edge-to-edge look:
generator.generate(
entity="album",
username="your_username",
cols=3,
rows=3,
corner_radius=12,
border_width=3,
border_color="#FF5A5F",
spacing=8,
)
๐ค GitHub Actions Automation
The repository ships a reusable composite action (action.yml) that generates a collage inside a workflow and writes it into the repository โ ideal for keeping a GitHub profile README fresh with weekly listening recaps.
Inputs: username (required), entity, cols, rows, period, output-path, mock, api-key, api-secret.
name: Weekly Last.fm Recap
on:
schedule:
- cron: "0 9 * * 1"
workflow_dispatch:
permissions:
contents: write
jobs:
recap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate weekly collage
uses: paurieraf/lastfm-collage-generator@v0.8.0
with:
username: ${{ secrets.LASTFM_USERNAME }}
entity: album
cols: "5"
rows: "5"
period: 7day
output-path: weekly-recap.png
api-key: ${{ secrets.LASTFM_API_KEY }}
api-secret: ${{ secrets.LASTFM_API_SECRET }}
- name: Commit updated collage
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add weekly-recap.png
git commit -m "chore: update weekly Last.fm recap" || echo "No changes"
git push
See .github/workflows/weekly-recap.yml.example for a ready-to-copy workflow. Set mock: "true" to validate workflows without Last.fm credentials.
๐ค Font Handling & Asset Packaging
The library bundles official TrueType fonts directly inside the distribution package at src/lastfmcollagegenerator/fonts/:
DejaVuSansMono.ttf(340 KB): Default monospace font for tile title and playcount rendering.DejaVuSansMono-Bold.ttf(332 KB): Bold monospace font variant.
Fonts are loaded dynamically relative to the package directory (os.path.dirname(__file__)), ensuring zero dependencies on host system fonts across macOS, Linux, and containerized Docker environments. Assets are declared in MANIFEST.in via recursive-include src/lastfmcollagegenerator/fonts *.ttf.
๐บ๏ธ Multi-Phase Feature Roadmap
Our development roadmap is organized across 4 strategic pillars and versioned milestones:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ STRATEGIC ROADMAP PILLARS โ
โโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 1. Visual Styling โ 2. Performance, Caching โ 3. Advanced Layouts โ 4. Ecosystem &
โ & Custom Themes โ & Resilience โ & Modern Formats โ CLI Integrations
โโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐จ Pillar 1: Visual Styling & Custom Themes
- Phase 1 (v0.5.0 โ Immediate Stability):
- Correct multi-row overlay geometry bug (
y_1 = y + self.TILE_HEIGHT). - Implement
generate_top_albums_collage(),generate_top_artists_collage(),generate_top_tracks_collage()convenience methods onCollageGenerator. - Implement strict lower boundary validation (
1 <= cols <= 5,1 <= rows <= 5). - Author comprehensive 100% offline pytest suite (>90% coverage).
- Correct multi-row overlay geometry bug (
- Phase 2 (v0.6.0 โ Visual Personalization):
- Dynamic Theme Engine: Pre-packaged themes (
Dark,Light,Glassmorphic,Sunset,Neon, and custom user hex palettes/Theme objects). - Typography & Auto-Scaling Engine: Word-boundary line breaking via
textwrap, dynamic font downscaling for long titles, and custom.ttf/.otffont path support. - Tile Geometry & Rounded Corners: Rounded squircle corner masking (
radius=12), configurable border stroke widths and colors, and inter-tile spacing margins. - Versatile Overlay Styles:
Banner(lower third),Full Tint(centered text),Gradient Fade,Minimalist Badge / Pill(rank + playcount chip), andClean Mode(show_text=Falsefor pure artwork grids).
- Dynamic Theme Engine: Pre-packaged themes (
โก Pillar 2: Performance, Caching & Scraping Resilience
- Phase 2 (v0.6.0 โ Fallbacks & Determinism):
- Dynamic Fallback Artwork Engine: Algorithmic two-color pastel gradients and initials typography derived from SHA-256 entity hashes to replace solid black tiles.
- Deterministic Secondary Sorting: Secondary sort key
(int(playcount), title)to ensure byte-for-byte identical collages on tied scrobble counts.
- Phase 3 (v0.7.0 โ Caching & Network Resilience):
- Multi-Tier Caching Subsystem: Tier-1 in-memory LRU cache (
maxsize=256) + Tier-2 SQLite persistent disk cache (~/.cache/lastfm-collage/) with 30-day TTL for album covers and 7-day TTL for scraped artist hero images. - Network Resilience Middleware: Token-bucket rate limiter (5 req/sec), exponential backoff with full jitter for transient HTTP errors, and circuit breaker for web scraping fallbacks.
- Multi-Tier Caching Subsystem: Tier-1 in-memory LRU cache (
- Phase 4 (v1.0.0 โ Asynchronous Architecture):
- Native AsyncIO Pipeline: Non-blocking concurrent asset acquisition via
httpx(async def generate_async()) with async semaphore concurrency throttling.
- Native AsyncIO Pipeline: Non-blocking concurrent asset acquisition via
๐ Pillar 3: Advanced Layouts & Modern Formats
- Phase 2 (v0.6.0 โ High-Density Grids):
- Arbitrary $N \times M$ Matrix Grids: Expand grid size beyond
5x5(up to20x20, max 400 tiles) with dynamic resolution auto-scaling (300px $\to$ 150px $\to$ 100px), configurable explicit tile sizes, and proportional typography.
- Arbitrary $N \times M$ Matrix Grids: Expand grid size beyond
- Phase 3 (v0.7.0 โ Social Presets & Backdrop Decorators):
- Social Media Dimension Presets: One-click generation for Instagram Story (
9:16$1080\times1920$), Instagram Post (1:1$1080\times1080$), Twitter Header (3:1$1500\times500$), and Desktop Wallpaper (16:9$1920\times1080$ / 4K). - Acrylic Backdrop Blur: Automatically fill non-square letterboxing with an acrylic Gaussian-blurred backdrop derived from the user's #1 top artwork.
- Social Media Dimension Presets: One-click generation for Instagram Story (
- Phase 4 (v1.0.0 โ Modern Formats & Asymmetric Grids):
- Modern Export Formats: Direct export to WebP (lossy/lossless), SVG vector containers with crisp
<text>nodes, and 300 DPI print-ready PDF posters. - Asymmetrical Layout Strategies:
Hero Grid(#1 item in $2\times2$ block, surrounded by $1\times1$ and $0.5\times0.5$ tiles),Bento Boxeditorial grids, andHoneycomb Hexagontessellations.
- Modern Export Formats: Direct export to WebP (lossy/lossless), SVG vector containers with crisp
- Phase 5 (v1.1.0 โ Motion Recaps):
- Animated Transitions: Generate animated GIF / MP4 listening recaps smoothly crossfading across time horizons (
7day$\to$1month$\to$3month$\to$12month).
- Animated Transitions: Generate animated GIF / MP4 listening recaps smoothly crossfading across time horizons (
๐ Pillar 4: Ecosystem & CLI Integrations
- Phase 2 (v0.6.0 โ Standalone CLI):
- Rich CLI Tool (
lastfm-collage): Global console script with rich terminal UI, colorized progress bars, download speed metrics, and ASCII art terminal previews.
- Rich CLI Tool (
- Phase 3 (v0.8.0 โ GitHub Actions Automation):
- GitHub Profile README Action (
action.yml): Automated scheduled workflow updating developer GitHub Profile READMEs with weekly listening recaps on cron.
- GitHub Profile README Action (
- Phase 5 (v1.1.0 - v1.2.0 โ Web Services & Chatbots):
- FastAPI Microservice Wrapper: Containerized REST API with OpenAPI docs and binary image streaming endpoints (
GET /api/v1/collage). - Discord, Telegram & Slack Bot Connectors: Slash commands (
/collage), user account binding, and direct embed image attachments.
- FastAPI Microservice Wrapper: Containerized REST API with OpenAPI docs and binary image streaming endpoints (
๐ Known Bugs & Defect Catalog
The following known defects in legacy versions (<= 0.4.13) have been diagnosed with root causes and remediation:
| Defect ID | Component | Severity | Description & Root Cause | Resolution Status |
|---|---|---|---|---|
| BUG-01 | collage.py:126-130 |
Critical | Multi-Row Overlay Coordinate Drift: y_1 = y * 2 + TILE_WIDTH causes exponential coordinate inflation on rows 1..4 (Row 1 banner height is 365px, completely covering Row 2). |
Fixed in v0.5.0 (y_1 = y + self.TILE_HEIGHT). |
| BUG-02 | collage_generator.py |
High | Documentation & API Signature Drift: generate_top_albums_collage(), generate_top_artists_collage(), and generate_top_tracks_collage() advertised in documentation but missing in code. |
Added in v0.5.0 facade. |
| BUG-03 | collage_generator.py:69 |
Medium | Incomplete Boundary Validation: _validate_parameters checks cols > 5 but allows cols <= 0 or rows <= 0, causing zero-dimension canvas crashes inside PIL. |
Enforced 1 <= cols <= 5 and 1 <= rows <= 5. |
| BUG-04 | collage.py:234, 251, 308 |
Medium | Scraping Fragility & Missing Timeouts: requests.get() lacks custom User-Agent, request timeouts, and catches only limited exceptions, crashing worker threads on CDN 502/503. |
Wrapped with DEFAULT_HEADERS, timeout=(3.05, 10.0), and blank tile fallbacks. |
| BUG-05 | collage.py:191 |
Low | Non-Deterministic Tile Ordering on Tied Plays: as_completed arrival jitter combined with single-key sort produces non-deterministic tile order on identical playcounts. |
Secondary sort key added: (int(playcount), title). |
๐ค Contributing
We welcome contributions from the community! To contribute:
- Fork the Repository and clone your fork locally.
- Initialize the Virtual Environment with
uv:uv sync - Create a Feature Branch:
git checkout -b feature/your-feature-name
- Author Tests: Ensure all new functionality is accompanied by 100% offline unit tests in
tests/using synthetic in-memory fixtures. - Execute the QA Suite:
uv run flake8 src/ tests/ uv run black --check src/ tests/ uv run mypy src/ uv run pytest tests/ -v
- Submit a Pull Request with a detailed explanation of your changes and test verification evidence.
Pull Request Quality Checklist
- All new methods have complete Python 3.8-compatible type annotations.
- No live network requests are made during test execution.
- All
PIL.Imageandio.BytesIOstreams are cleanly closed or managed via context managers. - HTTP requests specify explicit timeouts and custom
User-Agentheaders. - Pytest suite passes cleanly with zero warnings or failures.
๐ License
This project is open-source software licensed under the MIT License.
๐ค Authors & Acknowledgments
- Lead Author & Maintainer: @paurieraf (Pau Riera)
- Built With & Powered By:
- pylast โ Python Last.fm REST API v2.0 Client.
- Pillow (PIL Fork) โ Python Imaging Library for raster 2D graphics.
- Beautiful Soup 4 & html5lib โ Resilient HTML DOM scraping.
- uv โ Fast Python package and dependency manager by Astral.
- Monospace typography provided by the open-source DejaVu Fonts Project.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lastfmcollagegenerator-0.8.0.tar.gz.
File metadata
- Download URL: lastfmcollagegenerator-0.8.0.tar.gz
- Upload date:
- Size: 5.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f81665b3d84e59a46bcc611635bba0645dc1cd6b846dc59463108e7ba2fd060b
|
|
| MD5 |
2b4da0905c13d51b4c853e8476568a13
|
|
| BLAKE2b-256 |
2bff814b563e784b5e5aafe6b246013a4549c2e6020641044bac01662574743d
|
File details
Details for the file lastfmcollagegenerator-0.8.0-py3-none-any.whl.
File metadata
- Download URL: lastfmcollagegenerator-0.8.0-py3-none-any.whl
- Upload date:
- Size: 438.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.29 {"installer":{"name":"uv","version":"0.9.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e34f636eaca1417554dcee8b7e164a8ffce3e52fa33007c811d16dd4521be489
|
|
| MD5 |
33dfc7a271e1ebb202d380d7b6a92a44
|
|
| BLAKE2b-256 |
ea82e801f371518bfa7b3bd831cd2cbedef40e38f12dab33cf7e6a8d66897037
|