Automatic Cover Art
Relevant source files
The following files were used as context for generating this wiki page:The Automatic Cover Art system in SOFL enhances the user experience by automatically fetching, processing, and displaying cover images for games in the library. The primary source for online covers is SteamGridDB, but the system also supports local images and icons. It includes logic for image compositing to ensure a consistent and visually appealing presentation, even for images with non-standard aspect ratios.
Core Components
The cover art functionality is distributed across several key classes that manage fetching, processing, display, and user configuration.
| Class | File | Responsibility |
|---|---|---|
SgdbManager | sofl/store/managers/sgdb_manager.py | Manages interaction with the SteamGridDB API to search for and download game covers. |
CoverManager | sofl/store/managers/cover_manager.py | Processes and composites images from various sources (local file, icon, online URL) into final cover art. |
GameCover | sofl/game_cover.py | Holds the final cover Gdk.Texture and handles its display, including support for animated GIFs and placeholders. |
DetailsDialog | sofl/details_dialog.py | Provides the UI for manually selecting, changing, or deleting a game's cover. |
SOFLPreferences | sofl/preferences.py | Provides the UI for configuring SteamGridDB settings, such as the API key and fetch preferences. |
This diagram shows the relationships between the main components of the cover art system.
Configuration
Users can configure the SteamGridDB integration via the Preferences window. An API key is required to use the service.
Sources: sofl/preferences.py:101-110, 158-199
SteamGridDB Settings
These settings are located on the "SteamGridDB" page within the application's preferences.
| Setting | Widget | Schema Key | Description |
|---|---|---|---|
| API Key | sgdb_key_entry_row | sgdb-key | The user's SteamGridDB API key. The SGDB feature is disabled if this is empty. |
| Enable SteamGridDB | sgdb_switch | sgdb | Master switch to enable or disable fetching from SteamGridDB. |
| Prefer SteamGridDB | sgdb_prefer_switch | sgdb-prefer | If enabled, prioritizes covers from SteamGridDB over other sources. |
| Animated Covers | sgdb_animated_switch | sgdb-animated | If enabled, fetches animated covers (GIFs) when available. |
| Fetch All Covers | sgdb_fetch_button | N/A | A button to trigger a bulk fetch operation for all games in the library. |
Sources: sofl/preferences.py:101-110, 158-199, 214-220
The UI reflects the dependency on the API key by disabling the main sgdb switch if the key is not provided.
# sofl/preferences.py:222-227
def set_sgdb_sensitive(widget: Adw.EntryRow) -> None:
if not widget.get_text():
shared.schema.set_boolean("sgdb", False)
self.sgdb_switch.set_sensitive(widget.get_text())
Sources: sofl/preferences.py:222-227
Cover Processing Pipeline
The application follows a specific pipeline to find and apply a cover for a game. The CoverManager orchestrates this process.
Automatic Cover Fetching
When a new game is added and does not have a cover, the system automatically attempts to fetch one from SteamGridDB. This is triggered from the apply_preferences method in DetailsDialog.
The following sequence diagram illustrates the automatic fetching process for a new game.
Source Priority
The CoverManager defines a clear order of priority when processing a game to find a cover. It iterates through potential sources and uses the first valid one it finds.
This flowchart shows the decision logic in the CoverManager.
The keys local_image_path, local_icon_path, and online_cover_url are expected in the additional_data dictionary passed to the manager's main method.
Image Compositing
To maintain a uniform look, images that do not fit the standard cover aspect ratio are composited. The CoverManager.composite_cover method handles this logic.
- Stretchable Check: If an image is only slightly different in aspect ratio (taller or within a
max_stretchof 12%), it is simply stretched to fit. - Composite with Blur: If the image is not stretchable, a blurred background is created.
- The source image is downscaled (e.g., to 2x2 pixels) and then scaled up to the final cover size, creating a blurred, color-matched background.
- The original source image is then scaled down uniformly and centered on top of this blurred background.
- For icons (
local_icon_path), the centered image is scaled to 70% of the cover size to leave a larger border.
This diagram illustrates the compositing logic.
Manual Cover Management
Users can manage covers manually through the "Game Details" dialog.
Sources: [sofl/details_dialog.py]
Changing a Cover
- The user clicks the "Edit" button on the cover in
DetailsDialog. choose_coveris called, which opens aGtk.FileDialog.- The user selects an image file. Supported formats include standard ones like PNG and JPG, as well as SVG.
- The
set_covercallback is executed in a new thread.- It first checks if the image is an animated GIF. If so, it's converted.
- If not, it calls
CoverManager.composite_coverto process the image. - The resulting image path is passed to
game_cover.new_cover(). - The
cover_changedflag is set toTrue.
Sources: sofl/details_dialog.py:220-221, 401-402, 408-444
Deleting a Cover
- The user clicks the "Delete" button on the cover.
delete_pixbufis called, which invokesgame_cover.new_cover()with no path. This resets the cover to the default placeholder.- The
cover_changedflag is set toTrue.
Sources: sofl/details_dialog.py:219, 230-234
Display and Animation
The GameCover class is responsible for managing the final image texture and handling special cases like animated covers.
- Placeholders: If no cover is available,
GameCoverprovides a default SVG placeholder (library_placeholder.svg). - Animated GIFs: If the cover path points to a
.giffile, it is loaded as aGdkPixbuf.PixbufAnimation. A background thread (update_animation) repeatedly advances the animation iterator and updates theGtk.Picturewidget's texture, creating a looping animation in the UI. - Blurred Background:
GameCovercan also generate a blurred version of the cover for use in other UI elements, like the details page background. It does this by resizing the image, applying a Gaussian blur, and caching the result.