From b4420e334d2669d5ac29e70a6f66a0d84024d3fd Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Mon, 21 Jul 2025 20:44:30 +0200 Subject: [PATCH] small improvements --- hipsterfy/hipsterfy.py | 164 ++++++++++++++++++++++++++-------------- hipsterfy/panel_page.py | 111 +++++++++++++++++---------- 2 files changed, 180 insertions(+), 95 deletions(-) diff --git a/hipsterfy/hipsterfy.py b/hipsterfy/hipsterfy.py index 1ff890e..ff9759b 100644 --- a/hipsterfy/hipsterfy.py +++ b/hipsterfy/hipsterfy.py @@ -12,6 +12,11 @@ from PIL import Image from typing import List, Dict, Any import pandas as pd import re +from enum import Enum + +class CardStyle(str,Enum): + FULL_ALBUM_COVER = "full album cover" + MINIMAL = "minimal" class Hipsterfy(object): @@ -42,38 +47,25 @@ class HipsterfyPlaylistItem(object): return img_html - def generate_hipsterfy_front_card(self, include_preview:bool = False, print_mode:bool = False): + def generate_hipsterfy_front_card(self, include_preview:bool = False, card_style:CardStyle = CardStyle.FULL_ALBUM_COVER): # generates the square front card only with the centered qr code preview_html = self.embed_html if include_preview else "" if not self._qr_html: return "" - if print_mode: - # Minimalistisches Layout für Druck: kein Hintergrund, kein Schatten, kein Rahmen - card_style = """ - width: 300px; height: 300px; - border: 1.5px; - border-radius: 18px; - display: flex; - align-items: center; - justify-content: center; - background: none; - margin: 18px; - box-shadow: none; - transition: none; - """ - else: - card_style = """ - width: 300px; height: 300px; - border: 1.5px solid #222; - border-radius: 18px; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, #f9f9f9 60%, #e0e0e0 100%); - margin: 18px; - box-shadow: 0 6px 24px rgba(0,0,0,0.13), 0 1.5px 4px rgba(0,0,0,0.07); - transition: box-shadow 0.2s; - """ + + card_style = """ + width: 300px; height: 300px; + border: 1.5px; + border-radius: 18px; + display: flex; + align-items: center; + justify-content: center; + background: none; + margin: 18px; + box-shadow: none; + transition: none; + """ + return f"""
@@ -89,46 +81,108 @@ class HipsterfyPlaylistItem(object):
""" - def generate_hipsterfy_back_card(self, primary_property, secondary_property, additional_properties:List[str]=None, album_cover_gb:bool = True, print_mode:bool = False): - # generates the back card with primary and secondary property and additional properties + def generate_hipsterfy_back_card( + self, + primary_property, + secondary_property, + additional_properties: List[str] = None, + card_style: CardStyle = CardStyle.FULL_ALBUM_COVER + ): additional_html = "" if additional_properties: for prop in additional_properties: value = getattr(self, f"{prop}", "N/A") additional_html += f"
{prop.replace('_', ' ').title()}: {value}
" - # Albumcover als Hintergrund - album_bg = "" - if album_cover_gb and self.album_images and len(self.album_images) > 0: + # Style für die innere Box (Textfeld) + inner_box_style_full = """ + width: 250px; + height: 250px; + margin: auto; + background: rgba(0,0,0,0.35); + border-radius: 18px; + padding: 24px 18px 18px 18px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + box-sizing: border-box; + position: relative; + z-index: 1; + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); + """ + inner_box_style_minimal = """ + width: 250px; + height: 250px; + margin: auto; + background: #fff; + border-radius: 18px; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + padding: 24px 18px 18px 18px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + box-sizing: border-box; + position: relative; + z-index: 1; + """ + + # 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'] - album_bg = f""" - background: + outer_style = f""" + width: 300px; height: 300px; + border: 1.5px solid #222; + border-radius: 18px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: linear-gradient(135deg, rgba(30,30,30,0.65) 60%, rgba(0,0,0,0.85) 100%), url('{album_url}') center center/cover no-repeat; + margin: 18px; + box-shadow: 0 6px 24px rgba(0,0,0,0.13), 0 1.5px 4px rgba(0,0,0,0.07); + color: #fff; + overflow: hidden; + position: relative; + 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 + # MINIMAL: Weißer Hintergrund, schwarze Schrift, nur Kontur else: - album_bg = "background: #f9f9f9;" + outer_style = """ + width: 300px; height: 300px; + border: 2px solid #222; + border-radius: 18px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: #fff; + margin: 18px; + box-shadow: none; + color: #222; + overflow: hidden; + position: relative; + transition: box-shadow 0.2s; + """ + inner_box_style = inner_box_style_minimal + additional_html = additional_html.replace("color:#fff", "color:#222") + primary_color = "#222" + secondary_color = "#444" return f""" -
-
-

{getattr(self, secondary_property)}

-

{getattr(self, primary_property)}

+
+
+

{getattr(self, secondary_property)}

+

{getattr(self, primary_property)}

{additional_html}
diff --git a/hipsterfy/panel_page.py b/hipsterfy/panel_page.py index 6f7c132..7f42fd8 100644 --- a/hipsterfy/panel_page.py +++ b/hipsterfy/panel_page.py @@ -1,10 +1,13 @@ import panel as pn import base64 from io import BytesIO -from hipsterfy.hipsterfy import Hipsterfy, HipsterfyPlaylist, HipsterfyPlaylistItem +from hipsterfy.hipsterfy import Hipsterfy, HipsterfyPlaylist, HipsterfyPlaylistItem, CardStyle + pn.extension("filedownload", "notifications", "location") + + def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Template: # create widgets @@ -12,32 +15,43 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Templa primary_item = pn.widgets.Select(name='Primary Item', options=HipsterfyPlaylistItem.get_available_properties(), value="release_year") secondary_item = pn.widgets.Select(name='Secondary Item', options=HipsterfyPlaylistItem.get_available_properties(), value="title") additional_items = pn.widgets.MultiChoice(name='Additional Items', options=HipsterfyPlaylistItem.get_available_properties(), value=["artists", "album", "popularity"]) - enable_album_art = pn.widgets.Checkbox(name='Enable Album Art', value=True) - print_mode_in_preview = pn.widgets.Checkbox(name='Print Mode in preview', value=True) - cards_per_row_widget = pn.widgets.IntSlider(name="Cards per Row in print", start=1, end=6, value=4, step=1) + card_style = pn.widgets.Select(name='Card Style', options=[style.value for style in CardStyle], value=CardStyle.FULL_ALBUM_COVER.value) + cards_per_row_widget = pn.widgets.IntSlider(name="Cards per Row in print", start=1, end=8, value=4, step=1) pn.state.location.sync(playlist_uri, {"value": "playlist_uri"}) pn.state.location.sync(primary_item, {"value": "primary_item"}) pn.state.location.sync(secondary_item, {"value": "secondary_item"}) pn.state.location.sync(additional_items, {"value": "additional_items"}) - pn.state.location.sync(enable_album_art, {"value": "enable_album_art"}) + pn.state.location.sync(card_style, {"value": "card_style"}) + pn.state.location.sync(cards_per_row_widget, {"value": "cards_per_row"}) - create_preview_button = pn.widgets.Button(name='Create Preview', button_type='primary') + create_preview_button = pn.widgets.Button(name='Parse Playlist and Generate Cards', button_type='primary', sizing_mode='stretch_width') front_cards_column = pn.Column(sizing_mode='stretch_width') back_cards_column = pn.Column(sizing_mode='stretch_width') # FileDownload-Buttons (werden erst nach Preview-Generierung angezeigt) download_front_html_button = pn.widgets.FileDownload( - label="Download Front Cards (HTML)", button_type="success", visible=False, filename="hipsterfy_front_cards.html" + label="Download Front Cards (HTML)", button_type="success", visible=False, filename="hipsterfy_front_cards.html", sizing_mode='stretch_width' ) download_back_html_button = pn.widgets.FileDownload( - label="Download Back Cards (HTML)", button_type="success", visible=False, filename="hipsterfy_back_cards.html" + label="Download Back Cards (HTML)", button_type="success", visible=False, filename="hipsterfy_back_cards.html", sizing_mode='stretch_width' + ) + + playlist_instructions = pn.pane.HTML( + """ +
+

Playlist Instructions

+

Select any Spotify playlist and click the "Parse Playlist and Generate Cards" button to generate the cards.

+

Note: The playlist must be public accessible. Also dynamically individualized playlists (unfortunately a lot of spotify official playlists) Will not work.

+
+ """, + sizing_mode='stretch_width' ) print_instructions = pn.pane.HTML( """ -
+

Print Instructions

To print the cards:

    @@ -100,48 +114,65 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Templa pn.state.notifications.success("Download buttons updated successfully!. Download and print them from the sidebar") def create_preview(event): - pn.state.notifications.info("Generating preview for playlist...") - # clear previous cards - front_cards_column.clear() - back_cards_column.clear() + try: + pn.state.notifications.info("Generating preview for playlist...") - front_cards_column.append(pn.pane.HTML("

    Front Cards

    ", sizing_mode='stretch_width')) - back_cards_column.append(pn.pane.HTML("

    Back Cards

    ", sizing_mode='stretch_width')) + # clear previous cards + front_cards_column.clear() + back_cards_column.clear() - # get playlist URI - uri = playlist_uri.value.strip() - if not uri: - pn.state.notifications.error("Please enter a valid Spotify Playlist URI.") - return + front_cards_column.loading = True + back_cards_column.loading = True - # create HipsterfyPlaylist instance - hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy) + front_cards_column.append(pn.pane.HTML("

    Front Cards

    ", sizing_mode='stretch_width')) + back_cards_column.append(pn.pane.HTML("

    Back Cards

    ", sizing_mode='stretch_width')) - # generate front and back cards for each item in the playlist - for item in hipsterfy_playlist.get_tracks_data(): - front_card = item.generate_hipsterfy_front_card(include_preview=False, print_mode=print_mode_in_preview.value) - back_card = item.generate_hipsterfy_back_card( - primary_property=primary_item.value, - secondary_property=secondary_item.value, - additional_properties=additional_items.value, - album_cover_gb=enable_album_art.value, - print_mode=print_mode_in_preview.value - ) - front_cards_column.append(pn.pane.HTML(front_card, sizing_mode='stretch_width')) - back_cards_column.append(pn.pane.HTML(back_card, sizing_mode='stretch_width')) + # get playlist URI + uri = playlist_uri.value.strip() + if not uri: + pn.state.notifications.error("Please enter a valid Spotify Playlist URI.") + return - pn.state.notifications.success("Preview generated successfully!") - pn.state.notifications.info("Prepare to download the cards as HTML files...") - update_download_buttons() + # create HipsterfyPlaylist instance + hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy) + + new_back_cards = [] + new_front_cards = [] + + # generate front and back cards for each item in the playlist + for item in hipsterfy_playlist.get_tracks_data(): + front_card = item.generate_hipsterfy_front_card(include_preview=False, card_style=CardStyle(card_style.value)) + back_card = item.generate_hipsterfy_back_card( + primary_property=primary_item.value, + secondary_property=secondary_item.value, + additional_properties=additional_items.value, + card_style=CardStyle(card_style.value) + ) + new_front_cards.append(pn.pane.HTML(front_card, sizing_mode='stretch_width')) + new_back_cards.append(pn.pane.HTML(back_card, sizing_mode='stretch_width')) + + for card in new_front_cards: + front_cards_column.append(card) + for card in new_back_cards: + back_cards_column.append(card) + + pn.state.notifications.success("Preview generated successfully!") + pn.state.notifications.info("Prepare to download the cards as HTML files...") + update_download_buttons() + except Exception as e: + pn.state.notifications.error(f"Error generating preview: {str(e)}") + finally: + front_cards_column.loading = False + back_cards_column.loading = False create_preview_button.on_click(create_preview) template = pn.template.FastListTemplate( - title='Hipsterfy Playlist Manager', + title='Hipsterfy', sidebar=[ - playlist_uri, primary_item, secondary_item, additional_items, enable_album_art, + playlist_instructions, playlist_uri, primary_item, secondary_item, additional_items, card_style, cards_per_row_widget, - create_preview_button, print_instructions, download_front_html_button, download_back_html_button + create_preview_button, download_front_html_button, download_back_html_button, print_instructions ], main=[pn.Row(front_cards_column, back_cards_column, sizing_mode='stretch_width')], accent_base_color='indigo',