magic dithering
This commit is contained in:
		@ -14,9 +14,12 @@ import pandas as pd
 | 
			
		||||
import re
 | 
			
		||||
from enum import Enum
 | 
			
		||||
 | 
			
		||||
MAX_ALBUM_IMG_SIZE = 512
 | 
			
		||||
 | 
			
		||||
class CardStyle(str, Enum):
 | 
			
		||||
    FULL_ALBUM_COVER = "full album cover"
 | 
			
		||||
    MINIMAL = "minimal"
 | 
			
		||||
    PRINT_FRIENDLY_BW = "dithered bw album cover (slow, print friendly)"
 | 
			
		||||
 | 
			
		||||
class Hipsterfy(object):
 | 
			
		||||
    
 | 
			
		||||
@ -92,7 +95,7 @@ class HipsterfyPlaylistItem(object):
 | 
			
		||||
        if additional_properties:
 | 
			
		||||
            for prop in additional_properties:
 | 
			
		||||
                value = getattr(self, f"{prop}", "N/A")
 | 
			
		||||
                additional_html += f"<div style='margin: 4px 0; color:#fff;'><strong style='color:#fff;'>{prop.replace('_', ' ').title()}:</strong> <span style='color:#fff;'>{value}</span></div>"
 | 
			
		||||
                additional_html += f"<div style='margin: 4px 0; color:#e0e0e0;'><strong style='color:#fff;'>{prop.replace('_', ' ').title()}:</strong> <span style='color:#fff;'>{value}</span></div>"
 | 
			
		||||
 | 
			
		||||
        # Style für die innere Box (Textfeld)
 | 
			
		||||
        inner_box_style_full = """
 | 
			
		||||
@ -129,6 +132,54 @@ class HipsterfyPlaylistItem(object):
 | 
			
		||||
            z-index: 1;
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if CardStyle(card_style) == CardStyle.PRINT_FRIENDLY_BW and self.album_images and len(self.album_images) > 0:
 | 
			
		||||
            album_b64 = self.get_bw_album_img_base64()
 | 
			
		||||
            outer_style = f"""
 | 
			
		||||
                width: 300px; height: 300px;
 | 
			
		||||
                border: 2px solid #222;
 | 
			
		||||
                border-radius: 18px;
 | 
			
		||||
                display: flex;
 | 
			
		||||
                flex-direction: column;
 | 
			
		||||
                justify-content: center;
 | 
			
		||||
                align-items: center;
 | 
			
		||||
                background: url('data:image/png;base64,{album_b64}') center center/cover no-repeat;
 | 
			
		||||
                background-blend-mode: multiply;
 | 
			
		||||
                background-color: #fff;
 | 
			
		||||
                margin: 18px;
 | 
			
		||||
                box-shadow: none;
 | 
			
		||||
                color: #222;
 | 
			
		||||
                overflow: hidden;
 | 
			
		||||
                position: relative;
 | 
			
		||||
            """
 | 
			
		||||
            inner_box_style = """
 | 
			
		||||
                width: 250px;
 | 
			
		||||
                height: 250px;
 | 
			
		||||
                margin: auto;
 | 
			
		||||
                background: rgba(255,255,255,0.9);
 | 
			
		||||
                border-radius: 18px;
 | 
			
		||||
                box-shadow: 0 2px 8px rgba(0,0,0,0.15);
 | 
			
		||||
                padding: 18px 12px 12px 12px;
 | 
			
		||||
                display: flex;
 | 
			
		||||
                flex-direction: column;
 | 
			
		||||
                justify-content: center;
 | 
			
		||||
                align-items: center;
 | 
			
		||||
                box-sizing: border-box;
 | 
			
		||||
                position: relative;
 | 
			
		||||
                z-index: 1;
 | 
			
		||||
            """
 | 
			
		||||
            primary_color = "#222"
 | 
			
		||||
            secondary_color = "#444"
 | 
			
		||||
            additional_html = additional_html.replace("color:#fff", f"color:{secondary_color}")
 | 
			
		||||
            additional_html = additional_html.replace("color:#e0e0e0", f"color:{secondary_color}")
 | 
			
		||||
            return f"""
 | 
			
		||||
            <div style="{outer_style}">
 | 
			
		||||
                <div style="{inner_box_style}">
 | 
			
		||||
                    <p style="margin: 0 0 8px 0; font-size: 1.1em; color: {secondary_color};">{getattr(self, secondary_property)}</p>
 | 
			
		||||
                    <h3 style="margin: 0 0 8px 0; font-size: 1.25em; color: {primary_color};">{getattr(self, primary_property)}</h3>
 | 
			
		||||
                    {additional_html}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            """
 | 
			
		||||
        # FULL_ALBUM_COVER: Albumcover als Hintergrund, weiße Schrift, abgedunkelte Box
 | 
			
		||||
        if CardStyle(card_style) == CardStyle.FULL_ALBUM_COVER and self.album_images and len(self.album_images) > 0:
 | 
			
		||||
            album_url = self.album_images[0]['url']
 | 
			
		||||
@ -151,7 +202,6 @@ class HipsterfyPlaylistItem(object):
 | 
			
		||||
                transition: box-shadow 0.2s;
 | 
			
		||||
            """
 | 
			
		||||
            text_color = "#fff"
 | 
			
		||||
            additional_html = additional_html.replace("color:#fff", f"color:{text_color}")
 | 
			
		||||
            primary_color = "#fff"
 | 
			
		||||
            secondary_color = "#e0e0e0"
 | 
			
		||||
            inner_box_style = inner_box_style_full
 | 
			
		||||
@ -313,6 +363,32 @@ class HipsterfyPlaylistItem(object):
 | 
			
		||||
    def qr_html(self):
 | 
			
		||||
        return self._qr_html
 | 
			
		||||
    
 | 
			
		||||
    def get_bw_album_img_html(self):
 | 
			
		||||
        b64 = self.get_bw_album_img_base64()
 | 
			
		||||
        if b64:
 | 
			
		||||
            return f'<img src="data:image/png;base64,{b64}" style="width:{MAX_ALBUM_IMG_SIZE}px;height:{MAX_ALBUM_IMG_SIZE}px;border-radius:12px;box-shadow:0 2px 8px #aaa;margin-bottom:8px;" alt="Album Art BW"/>'
 | 
			
		||||
        return ""
 | 
			
		||||
    
 | 
			
		||||
    def get_bw_album_img_base64(self):
 | 
			
		||||
        if self.album_images and len(self.album_images) > 0:
 | 
			
		||||
            url = self.album_images[0]['url']
 | 
			
		||||
            try:
 | 
			
		||||
                import requests
 | 
			
		||||
                from PIL import Image, ImageOps, ImageEnhance
 | 
			
		||||
                response = requests.get(url)
 | 
			
		||||
                img = Image.open(BytesIO(response.content)).convert("L")
 | 
			
		||||
                img.thumbnail((MAX_ALBUM_IMG_SIZE, MAX_ALBUM_IMG_SIZE))
 | 
			
		||||
                img = ImageOps.autocontrast(img)
 | 
			
		||||
                img = ImageEnhance.Contrast(img).enhance(2.0)  # Kontrast erhöhen
 | 
			
		||||
                img = img.convert("1", dither=Image.FLOYDSTEINBERG, colors=12)  # Stärkeres Dithering
 | 
			
		||||
                buffer = BytesIO()
 | 
			
		||||
                img.save(buffer, format="PNG")
 | 
			
		||||
                img_str = base64.b64encode(buffer.getvalue()).decode()
 | 
			
		||||
                return img_str
 | 
			
		||||
            except Exception:
 | 
			
		||||
                return ""
 | 
			
		||||
        return ""
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
class HipsterfyPlaylist(object):
 | 
			
		||||
    def __init__(self, playlist_uri, hipsterfy:Hipsterfy):
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user