Skip to content

QR Codes

QR Code Generation

🎯 What the Package Does

The qr_generator package creates PNG QR‑code images that encode a mixtape’s public URL. Two flavours are offered:

Function Output Extra visual elements
generate_mixtape_qr Plain QR code (square) Optional centred logo (SVG/PNG)
generate_mixtape_qr_with_cover Composite image (cover → title banner → QR) Optional cover art, title banner, centred logo

Both functions return raw bytes (PNG data) ready to be sent as a Flask Response or saved to disk.

🔧 Installation & Dependencies

Setting Where it lives Default Remarks
qrcode (Python library) project.toml / uv.lock >=7.4 Required for both endpoints.
Pillow (image handling) project.toml >=10.0 Needed for compositing the logo/cover.
Static logo files static/images/logo.svg or static/logo.png The blueprint prefers SVG; falls back to PNG.
Cover directory app.config["COVER_DIR"] (set in config.py) collection-data/mixtapes/covers Used only by the download endpoint.
Cache-Control Hard-coded in the view (public, max-age=3600). Adjust in the source if you need a different TTL.

⚠️ Error Handling

Situation Raised Exception Message (visible to caller)
qrcode library missing ImportError (raised at the top of each public function) "qrcode library not installed. Install with: uv add qrcode pillow"
Logo or cover path does not exist Silently ignored — the function falls back to a plain QR or a QR without cover. Errors are printed to stdout (print) but not propagated.
Pillow fails to open an image (corrupt file) Caught inside the helper; continues processing Prints "Failed to load cover: …" or "Failed to add logo to QR code: …" and continues with the rest of the image.
Invalid size / qr_size (negative, zero) Not explicitly validated — Pillow raises a ValueError during resize Exception propagates to the caller (treated as a 500 error in the Flask view).

Best practice – Validate user‑supplied sizes before calling the functions, or wrap the call in a try/except block and return a friendly error page.

🔌 API

qr_generator

Enhanced QR Code generation with mixtape cover art and title.

Functions:

Name Description
generate_mixtape_qr_with_cover

Generate a branded QR code with mixtape cover art and title.

generate_mixtape_qr

Generate a simple QR code (backward compatibility).

generate_mixtape_qr_with_cover(url, title, cover_path=None, logo_path=None, qr_size=400, include_title=True)

Generate a branded QR code with mixtape cover art and title.

Creates a composite image with: - Mixtape cover art at the top - Title banner below cover - QR code at the bottom - Optional logo in QR center

Parameters:

Name Type Description Default
url str

The full URL to encode in the QR code

required
title str

The mixtape title to display

required
cover_path Path | None

Path to mixtape cover image

None
logo_path Path | None

Optional path to logo for QR center

None
qr_size int

Size of the QR code portion (default 400)

400
include_title bool

Whether to include title banner (default True)

True

Returns:

Name Type Description
bytes bytes

PNG image data of the complete branded QR code

Raises:

Type Description
ImportError

If qrcode library is not installed

Source code in src/qr_generator/qr_generator.py
def generate_mixtape_qr_with_cover(
    url: str,
    title: str,
    cover_path: Path | None = None,
    logo_path: Path | None = None,
    qr_size: int = 400,
    include_title: bool = True,
) -> bytes:
    """
    Generate a branded QR code with mixtape cover art and title.

    Creates a composite image with:
    - Mixtape cover art at the top
    - Title banner below cover
    - QR code at the bottom
    - Optional logo in QR center

    Args:
        url: The full URL to encode in the QR code
        title: The mixtape title to display
        cover_path: Path to mixtape cover image
        logo_path: Optional path to logo for QR center
        qr_size: Size of the QR code portion (default 400)
        include_title: Whether to include title banner (default True)

    Returns:
        bytes: PNG image data of the complete branded QR code

    Raises:
        ImportError: If qrcode library is not installed
    """
    if not QR_AVAILABLE:
        raise ImportError(
            "qrcode library not installed. Install with: uv add qrcode pillow"
        )

    # Generate base QR code
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=10,
        border=2,
    )

    qr.add_data(url)
    qr.make(fit=True)

    # Create styled QR code
    qr_img = qr.make_image(
        image_factory=StyledPilImage,
        module_drawer=RoundedModuleDrawer(),
        color_mask=SolidFillColorMask(
            back_color=(255, 255, 255),
            front_color=(30, 58, 95),  # Navy blue
        ),
    )

    qr_img = qr_img.resize((qr_size, qr_size), Image.Resampling.LANCZOS)

    # Add logo to QR center if provided
    if logo_path and logo_path.exists():
        qr_img = _add_logo_to_qr(qr_img, logo_path)

    # Create composite image with cover and title
    composite = _create_composite_image(
        qr_img=qr_img, title=title, cover_path=cover_path, include_title=include_title
    )

    # Convert to bytes
    buffer = io.BytesIO()
    composite.save(buffer, format="PNG", optimize=True)
    buffer.seek(0)

    return buffer.getvalue()

generate_mixtape_qr(url, title, logo_path=None, size=400)

Generate a simple QR code (backward compatibility).

This is the original function for basic QR generation.

Source code in src/qr_generator/qr_generator.py
def generate_mixtape_qr(
    url: str, title: str, logo_path: Path | None = None, size: int = 400
) -> bytes:
    """
    Generate a simple QR code (backward compatibility).

    This is the original function for basic QR generation.
    """
    if not QR_AVAILABLE:
        raise ImportError(
            "qrcode library not installed. Install with: uv add qrcode pillow"
        )

    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=10,
        border=2,
    )

    qr.add_data(url)
    qr.make(fit=True)

    img = qr.make_image(
        image_factory=StyledPilImage,
        module_drawer=RoundedModuleDrawer(),
        color_mask=SolidFillColorMask(
            back_color=(255, 255, 255), front_color=(30, 58, 95)
        ),
    )

    img = img.resize((size, size), Image.Resampling.LANCZOS)

    if logo_path and logo_path.exists():
        img = _add_logo_to_qr(img, logo_path)

    buffer = io.BytesIO()
    img.save(buffer, format="PNG", optimize=True)
    buffer.seek(0)

    return buffer.getvalue()