from qrcode.image.pil import PilImage
from qrcode.image.pure import PyPNGImage
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
from pathlib import Path
import json
import qrcode
from io import BytesIO
import base64
from PIL import Image
from typing import List, Dict, Any
import pandas as pd
import re
class Hipsterfy(object):
def __init__(self, client_id, client_secret):
self.client_id = client_id
self.client_secret = client_secret
self.sp = self.authenticate_spotify()
def authenticate_spotify(self):
"""
Authenticate with Spotify using Client Credentials Flow.
"""
credentials = SpotifyClientCredentials(client_id=self.client_id, client_secret=self.client_secret)
sp = spotipy.Spotify(client_credentials_manager=credentials)
return sp
class HipsterfyPlaylistItem(object):
def _generate_qr_code_base64(self, url):
qr: qrcode.QRCode[PilImage | PyPNGImage] = qrcode.QRCode(box_size=2, border=2)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffered = BytesIO()
img.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
return f'
'
def generate_hipsterfy_front_card(self, include_preview:bool = False):
# 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 ""
return f"""
{self._qr_html}
{preview_html}
Scan for Spotify Preview
"""
def generate_hipsterfy_back_card(self, primary_property, secondary_property, additional_properties:List[str]=None, album_cover_gb:bool = True):
# generates the back card with primary and secondary property and additional properties
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:
album_url = self.album_images[0]['url']
album_bg = f"""
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;
"""
else:
album_bg = "background: #f9f9f9;"
return f"""
{getattr(self, primary_property)}
{getattr(self, secondary_property)}
{additional_html}
"""
@staticmethod
def get_available_properties() -> List[str]:
"""
Returns a list of all available properties for the HipsterfyPlaylistItem.
This can be used to dynamically generate the back card with all properties.
"""
return [
'title', 'artists', 'album', 'release_date', 'release_year', 'duration', 'popularity',
'explicit', 'link', 'track_id', 'preview_url', 'track_number',
'disc_number', 'available_markets', 'is_local', 'external_ids',
'uri', 'album_images', 'album_type',
'album_release_date_precision', 'album_total_tracks',
'album_id', 'album_uri'
]
def __init__(self, playlist_item:dict):
self._raw_data = playlist_item
self._title = self._raw_data['name']
self._artists = ', '.join([artist['name'] for artist in self._raw_data['artists']])
self._album = self._raw_data['album']['name']
self._release_date = self._raw_data['album']['release_date']
self._duration_ms = self._raw_data['duration_ms']
self._duration_min = round(self._duration_ms / 60000, 2)
self._link = self._raw_data['external_urls']['spotify']
self._track_id = self._raw_data['id']
self._popularity = self._raw_data.get('popularity')
self._explicit = self._raw_data.get('explicit')
self._preview_url = self._raw_data.get('preview_url')
self._track_number = self._raw_data.get('track_number')
self._disc_number = self._raw_data.get('disc_number')
self._available_markets = self._raw_data.get('available_markets')
self._is_local = self._raw_data.get('is_local')
self._external_ids = self._raw_data.get('external_ids')
self._uri = self._raw_data.get('uri')
self._album_images = self._raw_data['album'].get('images')
self._album_type = self._raw_data['album'].get('album_type')
self._album_release_date_precision = self._raw_data['album'].get('release_date_precision')
self._album_total_tracks = self._raw_data['album'].get('total_tracks')
self._album_id = self._raw_data['album'].get('id')
self._album_uri = self._raw_data['album'].get('uri')
self._embed_html = ""
self._qr_html = ""
if self._track_id:
self._embed_html = f"""
"""
self._qr_html = self._generate_qr_code_base64(f"https://open.spotify.com/embed/track/{self._track_id}")
@property
def title(self):
return self._title
@property
def artists(self):
return self._artists
@property
def album(self):
return self._album
@property
def release_date(self):
return self._release_date
@property
def release_year(self):
return self._release_date.split('-')[0] if self._release_date else None
@property
def duration(self):
return self._duration_min
@property
def popularity(self):
return self._popularity
@property
def explicit(self):
return self._explicit
@property
def link(self):
return self._link
@property
def track_id(self):
return self._track_id
@property
def preview_url(self):
return self._preview_url
@property
def track_number(self):
return self._track_number
@property
def disc_number(self):
return self._disc_number
@property
def available_markets(self):
return self._available_markets
@property
def is_local(self):
return self._is_local
@property
def external_ids(self):
return self._external_ids
@property
def uri(self):
return self._uri
@property
def album_images(self):
return self._album_images
@property
def album_type(self):
return self._album_type
@property
def album_release_date_precision(self):
return self._album_release_date_precision
@property
def album_total_tracks(self):
return self._album_total_tracks
@property
def album_id(self):
return self._album_id
@property
def album_uri(self):
return self._album_uri
@property
def embed_html(self):
return self._embed_html
@property
def qr_html(self):
return self._qr_html
class HipsterfyPlaylist(object):
def __init__(self, playlist_uri, hipsterfy:Hipsterfy):
self._playlist_uri = playlist_uri
self._hipsterfy = hipsterfy
self._tracks_data = []
self._load_playlist()
def _load_playlist(self):
"""Load the playlist data from Spotify and extract track information.
"""
playlist_id = self._playlist_uri.split("/")[-1].split("?")[0]
results = self._hipsterfy.sp.playlist_items(playlist_id, additional_types=['track'])
self._tracks_data = [HipsterfyPlaylistItem(item['track']) for item in results['items'] if item['track']]
def get_tracks_data(self) -> List[HipsterfyPlaylistItem]:
"""Returns the list of HipsterfyPlaylistItem objects representing the tracks in the playlist.
"""
return self._tracks_data