🏡 index : quine-global/quintodrome.git

# Cover Art Archive Plugin for Navidrome
#
# This plugin fetches album cover art from the Cover Art Archive (https://coverartarchive.org/)
# using the MusicBrainz album MBID.
#
# Build with:
#   extism-py plugin/__init__.py -o coverartarchive-py.wasm

import base64
import extism
import json


@extism.import_fn("extism:host/user", "http_send")
def _http_send(offset: int) -> int: ...


def http_get(url):
    """GET url via Navidrome's HTTP host service. Returns (status_code, body_bytes)."""
    request = json.dumps({"request": {"method": "GET", "url": url}}).encode("utf-8")
    response_offset = _http_send(extism.memory.alloc(request).offset)
    resp = json.loads(extism.memory.string(extism.memory.find(response_offset)))
    if resp.get("error"):
        raise Exception(f"HTTP request failed: {resp['error']}")
    result = resp["result"]
    return result["statusCode"], base64.b64decode(result.get("body", ""))


@extism.plugin_fn
def nd_get_album_images():
    """Retrieve album cover images from Cover Art Archive."""
    input_data = extism.input_json()
    mbid = input_data.get("mbid", "")
    
    if not mbid:
        raise Exception("not found: MBID required")
    
    # Query Cover Art Archive API
    url = f"https://coverartarchive.org/release/{mbid}"
    status, body = http_get(url)

    if status != 200:
        raise Exception(f"not found: CAA returned status {status}")

    try:
        data = json.loads(body)
    except json.JSONDecodeError:
        raise Exception("not found: invalid JSON response")
    
    caa_images = data.get("images", [])
    if not caa_images:
        raise Exception("not found: no images in response")
    
    # Find the front cover image
    front_image = find_front_image(caa_images)
    if not front_image:
        raise Exception("not found: no front cover image")
    
    # Build the response with available image sizes
    images = build_image_list(front_image)
    if not images:
        raise Exception("not found: no usable image URLs")
    
    extism.output_str(json.dumps({"images": images}))


def find_front_image(images):
    """Find the front cover image from CAA response."""
    # First, look for an image explicitly marked as front
    for img in images:
        if img.get("front", False):
            return img
    
    # Second, look for an image with "Front" in types
    for img in images:
        types = img.get("types", [])
        if "Front" in types:
            return img
    
    # Fallback to first image
    if images:
        return images[0]
    
    return None


def build_image_list(img):
    """Build list of images with URLs and sizes from CAA image data."""
    images = []
    thumbnails = img.get("thumbnails", {})
    
    # First, try numeric sizes (250, 500, 1200, etc.)
    for size_str, url in thumbnails.items():
        if not url:
            continue
        try:
            size = int(size_str)
            images.append({"url": url, "size": size})
        except ValueError:
            pass  # Not a numeric size
    
    # If no numeric sizes, fallback to named sizes
    if not images:
        size_map = {"large": 500, "small": 250}
        for size_name, size in size_map.items():
            url = thumbnails.get(size_name)
            if url:
                images.append({"url": url, "size": size})
    
    # If still no images, use the main image URL
    if not images:
        main_url = img.get("image")
        if main_url:
            images.append({"url": main_url, "size": 0})
    
    return images