"""Terminal image rendering utilities for card previews."""
# Standard library imports
import io
from typing import Any, Optional
# Third-party imports
import requests
from PIL import Image
from rich.console import Console
from rich_pixels import Pixels
[docs]
def download_card_image(
image_url: str, max_size: tuple[int, int] = (300, 420)
) -> Optional[Image.Image]:
"""Download and resize a card image for terminal display.
Args:
image_url: URL of the card image
max_size: Maximum size (width, height) for the image
Returns:
PIL Image object or None if download fails
"""
try:
response = requests.get(image_url, timeout=10)
response.raise_for_status()
# Create image from response content
image = Image.open(io.BytesIO(response.content))
# Resize image while maintaining aspect ratio
image.thumbnail(max_size, Image.Resampling.LANCZOS)
return image
except (requests.RequestException, OSError):
# Silently fail - terminal image rendering is optional
return None
[docs]
def render_card_image_terminal(image_url: str, width: int = 40) -> Optional[Pixels]:
"""Render a card image as terminal pixels.
Args:
image_url: URL of the card image to render
width: Target width in terminal characters
Returns:
Pixels object for rich display or None if rendering fails
"""
# Calculate appropriate height (cards are roughly 63mm x 88mm, so ~1.4 aspect ratio)
height = int(width * 1.4)
# Download and resize image
image = download_card_image(image_url, max_size=(width * 2, height * 2))
if image is None:
return None
try:
# Convert to terminal pixels
pixels: Pixels = Pixels.from_image(image, resize=(width, height))
return pixels
except Exception:
# Silently fail if rendering doesn't work
return None
[docs]
def get_card_image_url(card_data: dict[str, Any]) -> Optional[str]:
"""Extract the image URL from card data.
Args:
card_data: Card data dictionary from NetrunnerDB API
Returns:
Image URL string or None if not found
"""
# NetrunnerDB provides images in different formats
# Try the main image URL first
image_url = card_data.get("image_url")
if image_url:
return str(image_url)
# Try alternative image fields
for field in ["imagesrc", "image", "card_image"]:
if field in card_data and card_data[field]:
return str(card_data[field])
# If no explicit image URL, construct it from the card code
# NetrunnerDB uses the pattern: https://card-images.netrunnerdb.com/v1/large/[code].jpg
card_code = card_data.get("code")
if card_code:
return f"https://card-images.netrunnerdb.com/v1/large/{card_code}.jpg"
return None
[docs]
def display_card_preview(
console: Console, card_data: dict[str, Any], title: str = "", width: int = 30
) -> bool:
"""Display a card preview image in the terminal.
Args:
console: Rich console for output
card_data: Card data from NetrunnerDB API
title: Optional title to display above the image
width: Width in terminal characters
Returns:
True if image was successfully displayed, False otherwise
"""
image_url = get_card_image_url(card_data)
if not image_url:
return False
pixels = render_card_image_terminal(image_url, width=width)
if pixels is None:
return False
if title:
console.print(f"\n[bold cyan]{title}[/bold cyan]")
console.print(pixels)
return True