Skip to main content

๐ŸŽต lastfm-collage-generator

Production-grade Python library for generating high-resolution visual composite image collages from Last.fm scrobble histories.

PyPI version Python Versions License: MIT uv Code style: black Checked with mypy Downloads PRs Welcome

Key Features โ€ข Grid Geometry โ€ข Architecture โ€ข Installation โ€ข Quickstart โ€ข API Reference โ€ข Developer Workflows โ€ข Testing & QA โ€ข 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 from https://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.
  • Configurable Grid Geometry: Generates rectangular or square grids from 1x1 up to 5x5 (e.g. 3x3 standard 9-tile grid, 5x5 25-tile poster, or asymmetric 3x5). Standard tile resolution is 300 x 300 pixels.
  • Comprehensive Time Horizons: Full support for all Last.fm aggregation periods: 7day (Weekly), 1month (Monthly), 3month (Quarterly), 6month (Semi-Annual), 12month (Yearly), and overall (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.ThreadPoolExecutor for 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 300x300 blank 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

Every collage is dynamically assembled from individual 300 x 300 pixel raster tiles:

Grid Size Total Tiles Dimensions (Width x Height) Aspect Ratio Megapixels Typical Use Case
1x1 1 tile 300 x 300 px 1:1 Square 0.09 MP Single item avatar / badge
2x2 4 tiles 600 x 600 px 1:1 Square 0.36 MP Compact widget / blog embed
3x3 9 tiles 900 x 900 px 1:1 Square 0.81 MP Standard social media card (#LastFmFriday, Instagram)
4x4 16 tiles 1200 x 1200 px 1:1 Square 1.44 MP Monthly scrobble recap poster
5x5 25 tiles 1500 x 1500 px 1:1 Square 2.25 MP High-density yearly / all-time overview
3x4 12 tiles 900 x 1200 px 3:4 Portrait 1.08 MP Mobile wallpaper / Instagram Story
4x3 12 tiles 1200 x 900 px 4:3 Landscape 1.08 MP Tablet wallpaper / forum signature
5x3 15 tiles 1500 x 900 px 5:3 Landscape 1.35 MP Desktop banner / Twitter header

Tile Layout Anatomy (300 x 300 px)

Each tile within the collage follows exact pixel geometry:

(x, y) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ”‚                                                              โ”‚
       โ”‚                                                              โ”‚
       โ”‚                   Tile Cover Artwork                         โ”‚
       โ”‚                 (Downloaded or Scraped)                      โ”‚
       โ”‚                     (300 x 300 px)                           โ”‚
       โ”‚                                                              โ”‚
       โ”‚                                                              โ”‚
y+235  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ—„โ”€โ”€ Banner Top (y + 235)
       โ”‚ Translucent Dark Banner: RGBA(0, 0, 0, 123) (Height: 65px)  โ”‚
y+240  โ”‚ Monospace Typography: DejaVuSansMono.ttf (15px regular/bold) โ”‚ โ—„โ”€โ”€ Text Origin (x + 8, y + 240)
       โ”‚ "Artist Name - Album / Track Title. (42 scrobbles)"          โ”‚
(x+300,โ”‚                                                              โ”‚
 y+300)โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ—„โ”€โ”€ Tile Bottom (y + 300)

๐Ÿ—๏ธ 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
) -> 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 <= 5 Required Number of horizontal grid columns.
rows int 1 <= rows <= 5 Required Number of vertical grid rows.
period str "7day", "1month", "3month", "6month", "12month", "overall" Required Scrobble aggregation time window.

Returns:

  • PIL.Image.Image: An allocated Pillow 24-bit RGB raster canvas with dimensions (cols * 300, rows * 300) pixels.

Exceptions Raised:

  • ValueError: If entity is not in ENTITIES, period is not in PERIODS, or cols/rows are outside 1..5.
  • 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 Top Artists Collage (5x5, monthly)
artist_collage = generator.generate_top_artists_collage(
    username="rj",
    cols=5,
    rows=5,
    period="1month"
)

# Generate Top Tracks Collage (4x3, overall history)
track_collage = generator.generate_top_tracks_collage(
    username="rj",
    cols=4,
    rows=3,
    period="overall"
)

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

๐Ÿ”ค 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 on CollageGenerator.
    • Implement strict lower boundary validation (1 <= cols <= 5, 1 <= rows <= 5).
    • Author comprehensive 100% offline pytest suite (>90% coverage).
  • Phase 2 (v0.6.0 โ€” Visual Personalization):
    • Dynamic Theme Engine: Pre-packaged themes (Dark, Light, Glassmorphic with localized Gaussian blur, Gradient Overlays, and custom user hex palettes).
    • Typography & Auto-Scaling Engine: Word-boundary line breaking via textwrap, dynamic font downscaling for long titles, and custom .ttf/.otf font 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), and Clean Mode (show_text=False for pure artwork grids).

โšก 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.
  • 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.

๐Ÿ“ 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 (e.g. 10x10 100-album grids) with dynamic tile resolution downscaling (300px $\to$ 150px $\to$ 100px) and memory-safe allocation.
  • 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.
  • 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 Box editorial grids, and Honeycomb Hexagon tessellations.
  • 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).

๐ŸŒ 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.
  • 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.
  • 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.

๐Ÿ› 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:

  1. Fork the Repository and clone your fork locally.
  2. Initialize the Virtual Environment with uv:
    uv sync
    
  3. Create a Feature Branch:
    git checkout -b feature/your-feature-name
    
  4. Author Tests: Ensure all new functionality is accompanied by 100% offline unit tests in tests/ using synthetic in-memory fixtures.
  5. Execute the QA Suite:
    uv run flake8 src/ tests/
    uv run black --check src/ tests/
    uv run mypy src/
    uv run pytest tests/ -v
    
  6. 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.Image and io.BytesIO streams are cleanly closed or managed via context managers.
  • HTTP requests specify explicit timeouts and custom User-Agent headers.
  • 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

lastfmcollagegenerator-0.5.0.tar.gz (716.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lastfmcollagegenerator-0.5.0-py3-none-any.whl (421.7 kB view details)

Uploaded Python 3

File details

Details for the file lastfmcollagegenerator-0.5.0.tar.gz.

File metadata

  • Download URL: lastfmcollagegenerator-0.5.0.tar.gz
  • Upload date:
  • Size: 716.1 kB
  • 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

Hashes for lastfmcollagegenerator-0.5.0.tar.gz
Algorithm Hash digest
SHA256 ecea7a536309a1b8dcce55243dded298552756659693a05f3a405213b8df192a
MD5 fb4f8a4ac3ab869c2d73dab71999ed4d
BLAKE2b-256 e810ee8a283159e08d855580697b92c47c7db41d648c50a9f2c59be297a0e089

See more details on using hashes here.

File details

Details for the file lastfmcollagegenerator-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: lastfmcollagegenerator-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 421.7 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

Hashes for lastfmcollagegenerator-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d2a749bc4abff5438b8a209e0db98571b62f3e65f174bb03ecef22fbf3dade0e
MD5 bc0aab2cfe53f878695e012516932725
BLAKE2b-256 4a2a67131dbba2a51fc876796bfb0a32c9a36efff109208a2518d5ebe2b5b53d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page