From 1771d86d7abc8fa191d7cf8bb5be2d1ef1d9d416 Mon Sep 17 00:00:00 2001 From: Wayne Stallwood Date: Sat, 13 Dec 2025 22:13:19 +0000 Subject: [PATCH 1/5] Rename original comic.py and add alternative metron version --- examples/spectra6/comics/{comic.py => comicvine_comic.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/spectra6/comics/{comic.py => comicvine_comic.py} (100%) diff --git a/examples/spectra6/comics/comic.py b/examples/spectra6/comics/comicvine_comic.py similarity index 100% rename from examples/spectra6/comics/comic.py rename to examples/spectra6/comics/comicvine_comic.py From d2d0f1b239d2ff64977c778606bcea9eb19d09cf Mon Sep 17 00:00:00 2001 From: Wayne Stallwood Date: Sat, 13 Dec 2025 22:14:54 +0000 Subject: [PATCH 2/5] Rename original comic.py and add alternative metron version --- examples/spectra6/comics/metron_comic.py | 226 +++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 examples/spectra6/comics/metron_comic.py diff --git a/examples/spectra6/comics/metron_comic.py b/examples/spectra6/comics/metron_comic.py new file mode 100644 index 00000000..eab48465 --- /dev/null +++ b/examples/spectra6/comics/metron_comic.py @@ -0,0 +1,226 @@ +""" +Python script to fetch a randomised comic cover from the Metron Comic API. + +This script displays comic book covers on an Inky Impression e-ink display. +You will need to sign up for an Account at https://metron.cloud/ to use this script. +Change the search query to the comic series you want to display! + +usage comic.py ["search string"] +""" + +import sys +import random +from io import BytesIO +from typing import Optional, List + +import mokkari +import requests +from PIL import Image +from inky.auto import auto + +# Your own config file to keep your credentials secret +from config import username, password + + +class ComicCoverFetcher: + """Handles fetching and displaying comic book covers.""" + + COVER_ATTRIBUTES = ['image', 'cover', 'cover_image', 'cover_url', 'image_url'] + + def __init__(self, username: str, password: str): + """ + Initialize the comic cover fetcher. + + Args: + username: Metron API username + password: Metron API password + """ + self.api = mokkari.api(username, password) + self.inky_display = auto() + + def search_comics(self, series_name: str) -> List: + """ + Search for comics by series name. + + Args: + series_name: The name of the comic series to search for + + Returns: + List of comic issues matching the search term + """ + print(f"Searching for comics matching: {series_name}") + results = self.api.issues_list({"series_name": series_name}) + print(f"\nFound {len(results)} results:") + + for idx, issue in enumerate(results, 1): + print(f"{idx}. {issue.issue_name} (ID: {issue.id})") + + return results + + def get_random_issue(self, results: List): + """ + Select a random issue from search results. + + Args: + results: List of comic issues + + Returns: + A randomly selected issue or None if results is empty + """ + if not results: + return None + return random.choice(results) + + def get_cover_url(self, issue_detail) -> Optional[str]: + """ + Extract the cover URL from an issue detail object. + + Args: + issue_detail: The detailed issue object from the API + + Returns: + Cover URL string or None if not found + """ + for attr in self.COVER_ATTRIBUTES: + if hasattr(issue_detail, attr): + cover_url = getattr(issue_detail, attr) + if cover_url: + print(f"Found cover URL in '{attr}' attribute: {cover_url}") + return cover_url + return None + + def download_image(self, url: str) -> Optional[Image.Image]: + """ + Download an image from a URL. + + Args: + url: The URL of the image to download + + Returns: + PIL Image object or None if download fails + """ + try: + response = requests.get(url, timeout=30) + response.raise_for_status() + image = Image.open(BytesIO(response.content)) + response.close() + return image + except requests.RequestException as e: + print(f"Error downloading image: {e}") + return None + + def process_image(self, image: Image.Image) -> Image.Image: + """ + Process image for display on Inky screen. + + Rotates portrait images to landscape and resizes to fit display. + + Args: + image: The PIL Image to process + + Returns: + Processed PIL Image + """ + # Rotate the image if it is taller than it is wide + if image.height > image.width: + image = image.rotate(90, expand=True) + + # Resize to match display resolution + image = image.resize(self.inky_display.resolution) + return image + + def display_on_inky(self, image: Image.Image) -> bool: + """ + Display an image on the Inky display. + + Args: + image: The PIL Image to display + + Returns: + True if successful, False otherwise + """ + try: + self.inky_display.set_image(image) + print("Updating Inky Impression!") + self.inky_display.show() + return True + except Exception as e: + print(f"Error updating display: {e}") + return False + + def print_debug_attributes(self, issue_detail): + """ + Print available attributes for debugging. + + Args: + issue_detail: The issue detail object to inspect + """ + print("\nNo cover image URL found. Available attributes:") + for attr in dir(issue_detail): + if not attr.startswith('_'): + print(f" - {attr}: {getattr(issue_detail, attr, 'N/A')}") + + def fetch_and_display(self, series_name: str) -> bool: + """ + Main workflow: search, fetch, and display a comic cover. + + Args: + series_name: The comic series to search for + + Returns: + True if successful, False otherwise + """ + # Search for comics + results = self.search_comics(series_name) + + if not results: + print(f"\nNo results found for '{series_name}'") + return False + + # Select random issue + random_issue = self.get_random_issue(results) + print(f"\nFetching details for: {random_issue.issue_name}") + + # Get full issue details + issue_detail = self.api.issue(random_issue.id) + + # Get cover URL + cover_url = self.get_cover_url(issue_detail) + + if not cover_url: + self.print_debug_attributes(issue_detail) + return False + + # Download image + image = self.download_image(cover_url) + if not image: + return False + + # Process and display + processed_image = self.process_image(image) + success = self.display_on_inky(processed_image) + + if success: + print("\nCover image downloaded and set successfully") + + return success + + +def main(): + """Main entry point for the script.""" + # Get search term from command line or use default + search_term = sys.argv[1] if len(sys.argv) > 1 else "batman" + + # Initialize fetcher and run + fetcher = ComicCoverFetcher(username, password) + + try: + fetcher.fetch_and_display(search_term) + except Exception as e: + print(f"\nError setting cover: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() + From 81a8bf675b77025e3d9694ae95d491c47b3d2cdf Mon Sep 17 00:00:00 2001 From: Wayne Stallwood Date: Sat, 13 Dec 2025 22:23:30 +0000 Subject: [PATCH 3/5] Added example config file --- examples/spectra6/comics/config.py.example | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 examples/spectra6/comics/config.py.example diff --git a/examples/spectra6/comics/config.py.example b/examples/spectra6/comics/config.py.example new file mode 100644 index 00000000..35652c47 --- /dev/null +++ b/examples/spectra6/comics/config.py.example @@ -0,0 +1,5 @@ +# Metron API credentials +# Copy this file to config.py and add your credentials + +username = "your_username_here" +password = "your_password_here" From 96db7793f639883bad3af72d48c8865dbe723ca7 Mon Sep 17 00:00:00 2001 From: Wayne Stallwood Date: Sat, 13 Dec 2025 22:38:03 +0000 Subject: [PATCH 4/5] Update README --- examples/spectra6/comics/README.md | 68 +++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/examples/spectra6/comics/README.md b/examples/spectra6/comics/README.md index 4f81a882..531a9480 100644 --- a/examples/spectra6/comics/README.md +++ b/examples/spectra6/comics/README.md @@ -1,8 +1,8 @@ -# comic.py +# comicvine_comic.py -`comic.py` fetches and displays a random comic book cover from the Comic Vine API. +`comicvine_comic.py` fetches and displays a random comic book cover from the Comic Vine API. -- [comic.py](#comicpy) +- [comicvine_comic.py](#comicpy) - [About Comic Vine](#about-comic-vine) - [Pre-requisites](#pre-requisites) - [Usage](#usage) @@ -24,10 +24,68 @@ You'll need to have the Inky library installed and your virtual environment acti 1. Get a Comic Vine API key: [https://comicvine.gamespot.com/api/](https://comicvine.gamespot.com/api/) 2. Edit the script to add your API key (`API_KEY` variable). -3. Run the script: `python comic.py` +3. Run the script: `python comicvine_comic.py` 4. The script will fetch a random comic cover and display it on your Inky Impression. ## Notes - You can change which volumes/series are searched by adding or removing strings from the `SEARCH_QUERIES` variable - the default is 'Weird Science', which looks like a great read. -- Set `RANDOM_VOLUME = True` to select a random volume from the top 5 search results instead of choosing the first one. We found this varied things up when there were multiple volumes with the same title. \ No newline at end of file +- Set `RANDOM_VOLUME = True` to select a random volume from the top 5 search results instead of choosing the first one. We found this varied things up when there were multiple volumes with the same title. + + +# metron_comic.py + +`metron_comic.py` fetches and displays a random "Batman" comic book cover from the Metron API. + +- [metron_comic.py](#comicpy) + - [About Metron](#about-Metron) + - [Pre-requisites](#pre-requisites) + - [Usage](#usage) + - [Notes](#notes) + + +## About Meton + +Metron is an alternative online database of comic book information. It has a free API that can be used to search and retrieve detailed information about comics, including cover images, issue details, and more. Check it out at https://metron.cloud/ + +## Pre-requisites + +You'll need to have the Inky library installed and your virtual environment activated: `source ~/.virtualenvs/pimoroni/bin/activate` + +## Usage + +```bash +python metron_comic.py [search_term] +``` + +**Examples:** +```bash +# Display a random Batman comic cover +python metron_comic.py batman + +# Display a random Spider-Man comic cover +python metron_comic.py "spider-man" + +# Uses default search term (batman) if none provided +python metron_comic.py +``` + +### Installation + +1. Configure your Metron API credentials: +```bash + cp config.py.example config.py +``` + +2. Edit `config.py` and add your Metron API credentials: +```python + username = "your_username" + password = "your_password" +``` + +### Getting Metron API Credentials + +1. Visit https://metron.cloud/ +2. Create a free account +3. Use your username and password in `config.py` + From bb8d59599918291f8008cc6692e7abbc8c184757 Mon Sep 17 00:00:00 2001 From: Wayne Stallwood Date: Sat, 13 Dec 2025 22:46:50 +0000 Subject: [PATCH 5/5] add metron to requirements_exmaples --- requirements-examples.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-examples.txt b/requirements-examples.txt index c89041ec..27eae180 100644 --- a/requirements-examples.txt +++ b/requirements-examples.txt @@ -11,3 +11,4 @@ geocoder pandas==2.2.3 # avoid 2.3.0 with no piwheels wheel seaborn wikiquotes +mokkari>=3.0.0