3 Commits
0.3.0 ... 0.4.0

Author SHA1 Message Date
500a77761f allow download raw project as json 2025-12-21 19:38:40 +01:00
7c24d9a7c5 fix mobile redirect 2025-08-04 17:24:39 +02:00
deb43a47f7 add mobile scanner 2025-08-04 17:15:39 +02:00
5 changed files with 62 additions and 13 deletions

View File

@ -391,7 +391,10 @@ class HipsterfyPlaylistItem(object):
class HipsterfyPlaylist(object):
def __init__(self, playlist_uri, hipsterfy:Hipsterfy):
def __init__(self, playlist_uri: str, hipsterfy:Hipsterfy, serialized_raw_data:List[Dict[str, Any]] = None ):
if serialized_raw_data:
self._tracks_data = [HipsterfyPlaylistItem(item) for item in serialized_raw_data]
return
self._playlist_uri = playlist_uri
self._hipsterfy = hipsterfy
self._tracks_data = []
@ -416,3 +419,8 @@ class HipsterfyPlaylist(object):
"""Returns the list of HipsterfyPlaylistItem objects representing the tracks in the playlist.
"""
return self._tracks_data
def get_raw_playlist_data(self) -> List[Dict[str, Any]]:
"""Returns the raw playlist data as a list of dictionaries.
"""
return [item._raw_data for item in self._tracks_data]

View File

@ -11,13 +11,14 @@ def parse_args():
parser.add_argument('--spotify_client_id', type=str, required=True, help='Spotify Client ID')
parser.add_argument('--spotify_client_secret', type=str, required=True, help='Spotify Client Secret')
parser.add_argument('--port', type=int, default=5006, help='Port to run the Panel app on')
parser.add_argument('--root_url', type=str, default=None, help='Root URL for the Panel app (optional)')
return parser.parse_args()
def panel_main():
args = parse_args()
hipsterfy = Hipsterfy(args.spotify_client_id, args.spotify_client_secret)
playlist_uri = 'https://open.spotify.com/playlist/5grJs3PKyLE0cL5NYmkGIF'
app = lambda: create_panel_page(hipsterfy, playlist_uri)
app = lambda: create_panel_page(hipsterfy, playlist_uri, args.root_url)
static_dir = os.path.join(os.path.dirname(__file__), "static")
pn.serve(

View File

@ -4,21 +4,34 @@ from io import BytesIO
from hipsterfy.hipsterfy import Hipsterfy, HipsterfyPlaylist, HipsterfyPlaylistItem, CardStyle
import qrcode
pn.extension("filedropper")
pn.extension("filedownload", "notifications", "location")
def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Template:
def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None, root_url = None) -> pn.Template:
# create widgets
mode = pn.widgets.RadioButtonGroup(name='Input Mode', options=['Playlist URI', 'Upload JSON'], button_type='success', value='Playlist URI', sizing_mode='stretch_width')
playlist_uri = pn.widgets.TextInput(name='Playlist URI', value=playlist_uri or '', placeholder='Enter Spotify Playlist URI')
json_upload = pn.widgets.FileDropper(name='Upload Playlist JSON', accepted_filetypes=['application/json'], sizing_mode='stretch_width')
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"])
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)
def on_mode_change(event):
if mode.value == 'Playlist URI':
playlist_uri.visible = True
json_upload.visible = False
else:
playlist_uri.visible = False
json_upload.visible = True
mode.param.watch(on_mode_change, 'value')
on_mode_change(None) # Initial call to set visibility
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"})
@ -39,6 +52,10 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Templa
label="Download Back Cards (HTML)", button_type="success", visible=False, filename="hipsterfy_back_cards.html", sizing_mode='stretch_width'
)
download_json_data_button = pn.widgets.FileDownload(
label="Download Playlist Data (JSON)", button_type="success", visible=False, filename="hipsterfy_raw_playlist_data.json", sizing_mode='stretch_width'
)
playlist_instructions = pn.pane.HTML(
"""
<div>
@ -110,6 +127,16 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Templa
download_back_html_button.callback = lambda: BytesIO(generate_html(cards_back, row_reverse=True))
download_back_html_button.visible = True
# Raw Playlist Data as JSON
def generate_playlist_json():
import json
uri = playlist_uri.value.strip()
hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy)
data = hipsterfy_playlist.get_raw_playlist_data()
return json.dumps(data, indent=4).encode() # FileDownload erwartet Bytes
download_json_data_button.callback = lambda: BytesIO(generate_playlist_json())
download_json_data_button.visible = True
print_instructions.visible = True # Show print instructions after generating preview
pn.state.notifications.success("Download buttons updated successfully!. Download and print them from the sidebar")
@ -135,6 +162,14 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Templa
return
# create HipsterfyPlaylist instance
if mode.value == 'Upload JSON':
if len(json_upload.value) == 0:
pn.state.notifications.error("Please upload a valid JSON file containing playlist data.")
return
import json
raw_data = json.loads(list(json_upload.value.values())[0].decode())
hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy, serialized_raw_data=raw_data)
else:
hipsterfy_playlist = HipsterfyPlaylist(uri, hipsterfy)
new_back_cards = []
@ -168,6 +203,8 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Templa
create_preview_button.on_click(create_preview)
# Dynamischer QR-Code für die Player-App
def generate_qr_code_base64(url):
qr = qrcode.QRCode(box_size=4, border=2)
@ -182,11 +219,14 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Templa
# Dynamisch: QR-Code aktualisiert sich, wenn die Seite unter einer anderen URL läuft
def player_qr_html():
# pn.state.location.href ist erst nach dem ersten Request gesetzt
base_url = pn.state.location.href or main_url
if root_url is None:
base_url = pn.state.location.href
# Basis-URL ohne evtl. Pfad/Query
from urllib.parse import urlparse
parsed = urlparse(base_url)
base = f"{parsed.scheme}://{parsed.netloc}"
else:
base = root_url
url = base + "/qr/scan.html"
return generate_qr_code_base64(url)
@ -196,9 +236,9 @@ def create_panel_page(hipsterfy: Hipsterfy, playlist_uri: str=None) -> pn.Templa
title='Hipsterfy',
sidebar=[
player_qr_pane, # <-- Dynamischer QR-Code ganz oben in der Sidebar
playlist_instructions, playlist_uri, primary_item, secondary_item, additional_items, card_style,
playlist_instructions, mode, json_upload, playlist_uri, primary_item, secondary_item, additional_items, card_style,
cards_per_row_widget,
create_preview_button, download_front_html_button, download_back_html_button, print_instructions
create_preview_button, download_front_html_button, download_back_html_button, download_json_data_button, print_instructions
],
main=[pn.Row(front_cards_column, back_cards_column, sizing_mode='stretch_width')],
accent_base_color='indigo',

View File

@ -110,7 +110,7 @@
</head>
<body>
<div id="app">
<h2>Spotify QR-Scanner</h2>
<h2>Hipsterfy QR-Scanner</h2>
<video id="video" autoplay playsinline></video>
<div id="result"></div>
<button id="backBtn">Back to scanner</button>

View File

@ -1,6 +1,6 @@
[project]
name = "hipsterfy"
version = "0.2.3"
version = "0.3.2"
description = ""
authors = [
{name="Jonas Weinz"}