Aktualizovat spotify_widget.py

This commit is contained in:
2026-09-24 16:54:00 +00:00
parent dcc2697176
commit 7e01723901

View File

@@ -1,243 +1,714 @@
import tkinter as tk import os
import requests import re
import threading import sys
import time import time
import os import math
import random import random
from io import BytesIO import datetime
from PIL import Image, ImageTk import threading
import subprocess
SERVER_URL = "http://localhost:8080/endpoint" import requests
CYCLE_INTERVAL_MS = 10000 # Změna produktu každých 10 sekund from io import BytesIO
import tkinter as tk
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) from PIL import Image, ImageTk, ImageDraw
import pythoncom
KATALOG_DIR = os.path.join(SCRIPT_DIR, "katalog")
if not os.path.exists(KATALOG_DIR): # Pokus o automatickou instalaci pycaw, pokud chybí
KATALOG_DIR = os.path.join(SCRIPT_DIR, "documents", "katalog") try:
from pycaw.pycaw import AudioUtilities
class FullscreenJarmarkDisplay: HAS_PYCAW = True
def __init__(self, root): except ImportError:
self.root = root print("📦 Instaluji potřebnou knihovnu 'pycaw' pro okamžité ztlumení Windows...")
self.root.title("RP + 1602 Display Jarmark") try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "pycaw", "comtypes"],
self.root.attributes("-fullscreen", True) creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
self.root.bind("<Escape>", lambda e: self.root.destroy()) from pycaw.pycaw import AudioUtilities
HAS_PYCAW = True
self.canvas = tk.Canvas(root, width=1920, height=1080, bg="#0d031b", highlightthickness=0) print("✅ Knihovna pycaw byla úspěšně nainstalována!")
self.canvas.pack(fill=tk.BOTH, expand=True) except Exception as e:
print(f"⚠️ Automatická instalace pycaw se nezdařila: {e}")
self.load_background() HAS_PYCAW = False
# Název produktu nahoře # Pokus o import winsound (standardně na Windows)
self.title_text_id = self.canvas.create_text(960, 120, text="", fill="white", font=("Impact", 75, "bold")) try:
import winsound
# Obrázek produktu uprostřed HAS_WINSOUND = True
self.prod_image_id = self.canvas.create_image(920, 510, anchor="center") except ImportError:
HAS_WINSOUND = False
# --- DESIGN CENOVKY (Vpravo) ---
# 1. Podkladový badge/karta pro cenu # Pokus o import pygame
self.price_bg_id = self.canvas.create_rectangle(1380, 450, 1840, 570, fill="#150826", outline="#9d4edd", width=3) try:
import pygame
# 2. Stín ceny (3D efekt) pygame.mixer.init()
self.price_shadow_id = self.canvas.create_text(1613, 513, text="", fill="#000000", font=("Trebuchet MS", 62, "bold"), anchor="center") HAS_PYGAME = True
except Exception:
# 3. Hlavní text ceny (Zářivě zlatá) HAS_PYGAME = False
self.price_text_id = self.canvas.create_text(1610, 510, text="", fill="#FFD700", font=("Trebuchet MS", 62, "bold"), anchor="center")
SERVER_URL = "http://localhost:8080/endpoint"
CYCLE_INTERVAL_MS = 10000 # Doba zobrazení produktu (10 sekund)
# Spodní lišta přehrávače FRAME_INTERVAL_MS = 120 # Rychlost 3D rotace fotek (120 ms)
self.canvas.create_rectangle(0, 910, 1920, 1080, fill="#000000", outline="") PARTICLE_COUNT = 60 # Počet bokeh částic
self.cover_img_id = self.canvas.create_image(80, 995, anchor="center") SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
self.spotify_title_id = self.canvas.create_text(160, 955, text="Načítám...", fill="white", font=("Arial", 22, "bold"), anchor="w") KATALOG_DIR = os.path.join(SCRIPT_DIR, "katalog")
self.spotify_subtitle_id = self.canvas.create_text(160, 990, text="Hudobné video • Čekám na Spotify", fill="#bbbbbb", font=("Arial", 16), anchor="w") if not os.path.exists(KATALOG_DIR):
KATALOG_DIR = os.path.join(SCRIPT_DIR, "documents", "katalog")
self.time_curr_id = self.canvas.create_text(160, 1040, text="0:00", fill="#aaaaaa", font=("Arial", 14), anchor="w")
self.time_total_id = self.canvas.create_text(1860, 1040, text="0:00", fill="#aaaaaa", font=("Arial", 14), anchor="e") # Složka se zvuky pro reklamy
SOUNDS_DIR = os.path.join(SCRIPT_DIR, "zvuky_reklama")
self.pbar_bg = self.canvas.create_line(220, 1040, 1800, 1040, fill="#444444", width=5) if not os.path.exists(SOUNDS_DIR):
self.pbar_fg = self.canvas.create_line(220, 1040, 220, 1040, fill="#ffffff", width=5) os.makedirs(SOUNDS_DIR, exist_ok=True)
self.running = True
self.current_img_url = None def natural_sort_key(s):
self.photo_cover = None """ Řazení souborů podle čísel (image1, image2, ..., image10) """
self.current_prod_photo = None return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', s)]
self.catalog_items = []
self.current_catalog_index = 0 def set_spotify_mute(mute=True):
""" Bleskově ztlumí VŠECHNY aplikace ve Windows kromě samotného Pythonu """
self.load_catalog() pythoncom.CoInitialize()
self.cycle_product() if HAS_PYCAW:
try:
threading.Thread(target=self.poll_data, daemon=True).start() sessions = AudioUtilities.GetAllSessions()
for session in sessions:
def load_background(self): try:
bg_candidates = [ pname = session.Process.name().lower() if session.Process else ""
"backgroung.png", if not any(keep in pname for keep in ["python", "wmplayer"]):
"background.png", volume = session.SimpleAudioVolume
"background.jpg", volume.SetMute(1 if mute else 0, None)
"backgroung.jpg" except Exception:
] pass
return
bg_path = None except Exception as e:
for filename in bg_candidates: print(f"Pycaw chyba: {e}")
full_path = os.path.join(SCRIPT_DIR, filename)
if os.path.exists(full_path): nircmd_path = os.path.join(SCRIPT_DIR, "nircmd.exe")
bg_path = full_path if os.path.exists(nircmd_path):
break try:
val = "1" if mute else "0"
if bg_path: for proc in ["spotify.exe", "chrome.exe", "msedge.exe", "brave.exe", "firefox.exe", "opera.exe"]:
try: subprocess.run(
bg_img = Image.open(bg_path).convert("RGBA").resize((1920, 1080), Image.Resampling.LANCZOS) [nircmd_path, "muteappvolume", proc, val],
self.bg_photo = ImageTk.PhotoImage(bg_img) creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0)
self.canvas.create_image(0, 0, image=self.bg_photo, anchor="nw") )
except Exception as e: except Exception as e:
print(f"Chyba při načítání pozadí {bg_path}: {e}") print(f"NirCmd chyba: {e}")
else:
colors = ["#2b0845", "#410b66", "#581287", "#731aa8", "#8b24ca"]
random.seed(42) class AdvancedParticle:
for _ in range(40): """ Třída pro vícevrstvé animované bokeh částice """
x = random.randint(0, 1920) def __init__(self, w=1920, h=1080):
y = random.randint(0, 1080) self.w = w
r = random.randint(40, 150) self.h = h
c = random.choice(colors) self.reset(random_y=True)
self.canvas.create_oval(x-r, y-r, x+r, y+r, fill=c, outline="")
def reset(self, random_y=False):
def load_catalog(self): self.x = random.uniform(0, self.w)
global KATALOG_DIR self.y = random.uniform(0, self.h) if random_y else self.h + random.uniform(20, 80)
if not os.path.exists(KATALOG_DIR):
KATALOG_DIR = os.path.join(SCRIPT_DIR, "katalog") self.p_type = random.choice(['bokeh_large', 'bokeh_medium', 'spark'])
if not os.path.exists(KATALOG_DIR):
KATALOG_DIR = os.path.join(SCRIPT_DIR, "documents", "katalog") if self.p_type == 'bokeh_large':
self.base_radius = random.uniform(40, 90)
self.catalog_items = [] self.speed_y = random.uniform(0.2, 0.6)
if not os.path.exists(KATALOG_DIR): self.sway_amp = random.uniform(1.0, 3.0)
print(f"Adresář 'katalog' neexistuje na cestě: {KATALOG_DIR}") self.sway_freq = random.uniform(0.005, 0.015)
return self.color_outer = random.choice(["#2a0845", "#3c096c", "#1e0034", "#480ca8", "#38040e"])
self.color_inner = random.choice(["#5a189a", "#7b2cbf", "#9d4edd", "#7209b7", "#9e2a2b"])
for folder_name in sorted(os.listdir(KATALOG_DIR)): elif self.p_type == 'bokeh_medium':
folder_path = os.path.join(KATALOG_DIR, folder_name) self.base_radius = random.uniform(18, 38)
if os.path.isdir(folder_path): self.speed_y = random.uniform(0.5, 1.2)
info_path = os.path.join(folder_path, "info.txt") self.sway_amp = random.uniform(1.5, 4.0)
image_path = os.path.join(folder_path, "image.png") self.sway_freq = random.uniform(0.01, 0.025)
self.color_outer = random.choice(["#5a189a", "#7209b7", "#b5179e", "#480ca8"])
title = folder_name self.color_inner = random.choice(["#e0aaff", "#f72585", "#c77dff", "#ffb703"])
price = "" else: # spark
self.base_radius = random.uniform(2, 5)
if os.path.exists(info_path): self.speed_y = random.uniform(0.8, 2.2)
try: self.sway_amp = random.uniform(0.5, 2.0)
with open(info_path, "r", encoding="utf-8") as f: self.sway_freq = random.uniform(0.02, 0.05)
lines = [line.strip() for line in f.readlines() if line.strip()] self.color_outer = "#ffffff"
if len(lines) >= 1: self.color_inner = random.choice(["#ffffff", "#ffee83", "#4cc9f0", "#ffb703"])
title = lines[0]
if len(lines) >= 2: self.radius = self.base_radius
price = lines[1] self.pulse_phase = random.uniform(0, 2 * math.pi)
except Exception as e: self.pulse_speed = random.uniform(0.015, 0.04)
print(f"Chyba při čtení {info_path}: {e}") self.phase = random.uniform(0, 2 * math.pi)
self.catalog_items.append({ def update(self, w, h):
"title": title, self.w = w
"price": price, self.h = h
"image_path": image_path if os.path.exists(image_path) else None self.y -= self.speed_y
}) self.phase += self.sway_freq
self.x += math.sin(self.phase) * self.sway_amp
def cycle_product(self):
if self.current_catalog_index == 0: self.pulse_phase += self.pulse_speed
self.load_catalog() pulse_factor = 1.0 + 0.22 * math.sin(self.pulse_phase)
self.radius = self.base_radius * pulse_factor
if self.catalog_items:
self.current_catalog_index %= len(self.catalog_items) if self.y < -self.radius * 2 or self.x < -self.radius or self.x > self.w + self.radius:
item = self.catalog_items[self.current_catalog_index] self.reset(random_y=False)
self.canvas.itemconfig(self.title_text_id, text=item["title"])
class FullscreenJarmarkDisplay:
# Nastavení ceny i stínu ceny def __init__(self, root):
price_val = item["price"] self.root = root
self.canvas.itemconfig(self.price_text_id, text=price_val) self.root.title("Jarmark Display Kiosk")
self.canvas.itemconfig(self.price_shadow_id, text=price_val) self.root.attributes("-fullscreen", True)
# Pokud cenovka nemá text, schováme podkladový obdélník self.w = 1920
if price_val: self.h = 1080
self.canvas.itemconfig(self.price_bg_id, state="normal")
else: # Klávesové zkratky
self.canvas.itemconfig(self.price_bg_id, state="hidden") self.root.bind("<Escape>", lambda e: self.root.destroy())
self.root.bind("<Right>", lambda e: self.next_product())
if item["image_path"]: self.root.bind("<Left>", lambda e: self.prev_product())
try: self.root.bind("<space>", lambda e: self.toggle_pause())
raw_img = Image.open(item["image_path"]).convert("RGBA") self.root.bind("<r>", lambda e: self.reload_catalog_action())
raw_img.thumbnail((700, 460), Image.Resampling.LANCZOS) self.root.bind("<R>", lambda e: self.reload_catalog_action())
self.current_prod_photo = ImageTk.PhotoImage(raw_img)
self.canvas.itemconfig(self.prod_image_id, image=self.current_prod_photo) # Plátno s čistým tmavým pozadím
except Exception as e: self.canvas = tk.Canvas(root, bg="#07010f", highlightthickness=0)
print(f"Chyba při načítání obrázku {item['image_path']}: {e}") self.canvas.pack(fill=tk.BOTH, expand=True)
self.canvas.itemconfig(self.prod_image_id, image="")
else: self.canvas.bind("<Configure>", self.on_resize)
self.canvas.itemconfig(self.prod_image_id, image="")
# Částice
self.current_catalog_index = (self.current_catalog_index + 1) % len(self.catalog_items) self.particles = [AdvancedParticle(self.w, self.h) for _ in range(PARTICLE_COUNT)]
self.particle_draw_ids = []
self.root.after(CYCLE_INTERVAL_MS, self.cycle_product) self.init_particles_canvas()
def poll_data(self): # Canvas Prvky - Název produktu se stínem pro skvělou čitelnost bez rámčku
while self.running: self.title_shadow_id = self.canvas.create_text(0, 0, text="", fill="#000000", font=("Impact", 42, "bold"), anchor="center")
try: self.title_text_id = self.canvas.create_text(0, 0, text="", fill="#ffffff", font=("Impact", 42, "bold"), anchor="center")
res = requests.get(SERVER_URL, timeout=1)
if res.status_code == 200: self.badge_bg_id = self.canvas.create_rectangle(0, 0, 0, 0, fill="", outline="", state="hidden")
data = res.json() self.badge_text_id = self.canvas.create_text(0, 0, text="", fill="white", font=("Segoe UI", 13, "bold"), state="hidden")
artist = data.get("artist", "Neznámý interpret")
title = data.get("title", "Čekám na skladbu") # Produkt fotka (zcela bez rámečku, volně na pozadí)
progress = data.get("progressTime", "0:00/0:00") self.prod_image_id = self.canvas.create_image(0, 0, anchor="center")
img_url = data.get("coverImg", None)
# Cenovka vpravo
self.root.after(0, self.update_gui, artist, title, progress, img_url) self.price_bg_id = self.canvas.create_rectangle(0, 0, 0, 0, fill="#120521", outline="#9d4edd", width=2)
except Exception: self.price_shadow_id = self.canvas.create_text(0, 0, text="", fill="#000000", font=("Segoe UI", 38, "bold"), anchor="center")
pass self.price_text_id = self.canvas.create_text(0, 0, text="", fill="#ffb703", font=("Segoe UI", 38, "bold"), anchor="center")
time.sleep(0.8)
# Hodiny & Datum
def parse_time_to_seconds(self, time_str): self.clock_time_id = self.canvas.create_text(0, 0, text="", fill="#ffffff", font=("Segoe UI", 26, "bold"), anchor="e")
try: self.clock_date_id = self.canvas.create_text(0, 0, text="", fill="#9d4edd", font=("Segoe UI", 13, "bold"), anchor="e")
parts = time_str.split(':')
return int(parts[0]) * 60 + int(parts[1]) # Indikátor pauzy
except Exception: self.pause_indicator_id = self.canvas.create_text(0, 0, text="", fill="#ff0055", font=("Segoe UI", 13, "bold"), anchor="w")
return 0
# Spotify Karta (ovládaná vaším rozšířením, vlevo dole)
def update_gui(self, artist, title, progress, img_url): self.card_x1, self.card_y1 = 0, 0
self.canvas.itemconfig(self.spotify_title_id, text=title) self.card_w, self.card_h = 360, 150
self.canvas.itemconfig(self.spotify_subtitle_id, text=f"Hudobné video • {artist}") self.card_bg_photo = None
self.card_bg_id = self.canvas.create_image(0, 0, anchor="nw")
if "/" in progress:
curr, dur = progress.split("/") self.cover_img_id = self.canvas.create_image(0, 0, anchor="center")
self.canvas.itemconfig(self.time_curr_id, text=curr) self.spotify_title_id = self.canvas.create_text(0, 0, text="Načítám...", fill="white", font=("Segoe UI", 13, "bold"), anchor="w")
self.canvas.itemconfig(self.time_total_id, text=dur) self.spotify_subtitle_id = self.canvas.create_text(0, 0, text="Čekám na Spotify", fill="#c77dff", font=("Segoe UI", 10), anchor="w")
c_sec = self.parse_time_to_seconds(curr) self.time_curr_id = self.canvas.create_text(0, 0, text="0:00", fill="#aaaaaa", font=("Segoe UI", 9), anchor="w")
d_sec = self.parse_time_to_seconds(dur) self.time_total_id = self.canvas.create_text(0, 0, text="0:00", fill="#aaaaaa", font=("Segoe UI", 9), anchor="e")
percent = (c_sec / d_sec) if d_sec > 0 else 0
self.pbar_bg = self.canvas.create_line(0, 0, 0, 0, fill="#3c096c", width=5, capstyle=tk.ROUND)
start_x = 220 self.pbar_fg = self.canvas.create_line(0, 0, 0, 0, fill="#f72585", width=5, capstyle=tk.ROUND)
max_w = 1800 - start_x
fill_x = start_x + int(max_w * percent) # Stavové proměnné
self.canvas.coords(self.pbar_fg, start_x, 1040, fill_x, 1040) self.running = True
self.is_paused = False
if img_url and img_url != self.current_img_url: self.is_ad_active = False
self.current_img_url = img_url self.ad_sound_process = None
threading.Thread(target=self.download_cover, args=(img_url,), daemon=True).start() self.current_img_url = None
self.photo_cover = None
def download_cover(self, url): self.raw_cover_img = None
try: self.current_card_rgba = (25, 10, 40, 230)
res = requests.get(url, timeout=3) self.current_prod_photo = None
if res.status_code == 200:
raw_img = Image.open(BytesIO(res.content)).convert("RGBA") self.catalog_items = []
raw_img = raw_img.resize((100, 100), Image.Resampling.LANCZOS) self.active_item = None
self.current_catalog_index = 0
self.photo_cover = ImageTk.PhotoImage(raw_img) self.current_frame_index = 0
self.root.after(0, lambda: self.canvas.itemconfig(self.cover_img_id, image=self.photo_cover))
except Exception: # Spuštění procesů
pass self.load_catalog()
self.cycle_product()
if __name__ == "__main__": self.animate_product_3d()
root = tk.Tk() self.animate_particles()
app = FullscreenJarmarkDisplay(root) self.update_clock()
threading.Thread(target=self.poll_data, daemon=True).start()
def trigger_ad_sound(self):
print("🔇 Ztlumuji vše kromě Pythonu...")
set_spotify_mute(True)
if not os.path.exists(SOUNDS_DIR):
return
valid_exts = ('.wav', '.mp3', '.wma', '.ogg')
sounds = [os.path.join(SOUNDS_DIR, f) for f in os.listdir(SOUNDS_DIR) if f.lower().endswith(valid_exts)]
if not sounds:
return
chosen_sound = random.choice(sounds)
def _play():
try:
if HAS_PYGAME:
pygame.mixer.music.load(chosen_sound)
pygame.mixer.music.play()
elif chosen_sound.lower().endswith('.wav') and HAS_WINSOUND:
winsound.PlaySound(chosen_sound, winsound.SND_FILENAME | winsound.SND_ASYNC)
else:
ps_script = f"$m = New-Object -ComObject WMPlayer.OCX; $m.URL = '{chosen_sound}'; $m.controls.play(); Start-Sleep -s 60"
self.ad_sound_process = subprocess.Popen(
["powershell", "-Command", ps_script],
creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0)
)
except Exception as e:
print(f"Chyba při přehrávání zvuku: {e}")
threading.Thread(target=_play, daemon=True).start()
def stop_ad_sound(self):
print("🔊 REKLAMA SKONČILA: Vracím hlasitost zpět.")
try:
if HAS_PYGAME and pygame.mixer.music.get_busy():
pygame.mixer.music.stop()
if HAS_WINSOUND:
winsound.PlaySound(None, winsound.SND_PURGE)
if self.ad_sound_process:
self.ad_sound_process.kill()
self.ad_sound_process = None
except Exception as e:
print(f"Chyba při zastavování: {e}")
set_spotify_mute(False)
def on_resize(self, event):
if event.width <= 10 or event.height <= 10:
return
if self.w == event.width and self.h == event.height:
return
self.w = event.width
self.h = event.height
self.reposition_all()
def reposition_all(self):
w, h = self.w, self.h
# 1. Název produktu + stín
font_title_sz = max(24, int(h * 0.055))
self.canvas.itemconfig(self.title_text_id, font=("Impact", font_title_sz, "bold"))
self.canvas.itemconfig(self.title_shadow_id, font=("Impact", font_title_sz, "bold"))
self.canvas.coords(self.title_text_id, w * 0.5, h * 0.09)
self.canvas.coords(self.title_shadow_id, w * 0.5 + 2, h * 0.09 + 2)
# 2. Produkt
self.canvas.coords(self.prod_image_id, w * 0.5, h * 0.48)
self.update_product_frame()
# 3. Cenovka vpravo
font_price_sz = max(22, int(h * 0.050))
price_w, price_h = int(w * 0.20), int(h * 0.10)
px1, py1 = int(w * 0.74), int(h * 0.46)
px2, py2 = px1 + price_w, py1 + price_h
self.canvas.coords(self.price_bg_id, px1, py1, px2, py2)
self.canvas.coords(self.price_shadow_id, px1 + price_w/2 + 2, py1 + price_h/2 + 2)
self.canvas.coords(self.price_text_id, px1 + price_w/2, py1 + price_h/2)
self.canvas.itemconfig(self.price_shadow_id, font=("Segoe UI", font_price_sz, "bold"))
self.canvas.itemconfig(self.price_text_id, font=("Segoe UI", font_price_sz, "bold"))
# 4. Hodiny & Datum
font_time_sz = max(18, int(h * 0.032))
font_date_sz = max(11, int(h * 0.016))
self.canvas.itemconfig(self.clock_time_id, font=("Segoe UI", font_time_sz, "bold"))
self.canvas.itemconfig(self.clock_date_id, font=("Segoe UI", font_date_sz, "bold"))
self.canvas.coords(self.clock_time_id, w * 0.97, h * 0.04)
self.canvas.coords(self.clock_date_id, w * 0.97, h * 0.08)
# 5. Indikátor pauzy
self.canvas.coords(self.pause_indicator_id, w * 0.03, h * 0.04)
# 6. Spotify Karta vlevo dole
self.card_w = max(320, int(w * 0.23))
self.card_h = max(135, int(h * 0.16))
self.card_x1 = int(w * 0.03)
self.card_y1 = int(h - self.card_h - (h * 0.03))
self.canvas.coords(self.card_bg_id, self.card_x1, self.card_y1)
self.update_card_background(self.current_card_rgba)
cover_size = int(self.card_h * 0.58)
cover_cx = self.card_x1 + int(self.card_h * 0.42)
cover_cy = self.card_y1 + int(self.card_h * 0.50)
self.canvas.coords(self.cover_img_id, cover_cx, cover_cy)
if self.raw_cover_img:
resized_cover = self.raw_cover_img.resize((cover_size, cover_size), Image.Resampling.LANCZOS)
self.photo_cover = ImageTk.PhotoImage(resized_cover)
self.canvas.itemconfig(self.cover_img_id, image=self.photo_cover)
text_x = self.card_x1 + int(self.card_h * 0.82)
font_sp_title = max(11, int(self.card_h * 0.11))
font_sp_sub = max(9, int(self.card_h * 0.085))
self.canvas.itemconfig(self.spotify_title_id, font=("Segoe UI", font_sp_title, "bold"))
self.canvas.itemconfig(self.spotify_subtitle_id, font=("Segoe UI", font_sp_sub, "bold"))
self.canvas.coords(self.spotify_title_id, text_x, self.card_y1 + int(self.card_h * 0.22))
self.canvas.coords(self.spotify_subtitle_id, text_x, self.card_y1 + int(self.card_h * 0.40))
bar_x1 = text_x
bar_x2 = self.card_x1 + self.card_w - int(self.card_h * 0.12)
bar_y = self.card_y1 + int(self.card_h * 0.68)
self.canvas.coords(self.pbar_bg, bar_x1, bar_y, bar_x2, bar_y)
self.canvas.coords(self.time_curr_id, bar_x1, bar_y + int(self.card_h * 0.18))
self.canvas.coords(self.time_total_id, bar_x2, bar_y + int(self.card_h * 0.18))
if self.active_item:
self.show_product_by_index(self.current_catalog_index - 1)
def update_clock(self):
now = datetime.datetime.now()
time_str = now.strftime("%H:%M:%S")
days_cz = ["Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota", "Neděle"]
months_cz = ["ledna", "února", "března", "dubna", "května", "června", "července", "srpna", "září", "října", "listopadu", "prosince"]
day_name = days_cz[now.weekday()]
month_name = months_cz[now.month - 1]
date_str = f"{day_name}, {now.day}. {month_name} {now.year}"
self.canvas.itemconfig(self.clock_time_id, text=time_str)
self.canvas.itemconfig(self.clock_date_id, text=date_str)
self.root.after(1000, self.update_clock)
def init_particles_canvas(self):
for p in self.particles:
if p.p_type in ['bokeh_large', 'bokeh_medium']:
outer_id = self.canvas.create_oval(p.x - p.radius, p.y - p.radius, p.x + p.radius, p.y + p.radius, fill=p.color_outer, outline="")
r_in = p.radius * 0.45
inner_id = self.canvas.create_oval(p.x - r_in, p.y - r_in, p.x + r_in, p.y + r_in, fill=p.color_inner, outline="")
self.particle_draw_ids.append((outer_id, inner_id))
else:
draw_id = self.canvas.create_oval(p.x - p.radius, p.y - p.radius, p.x + p.radius, p.y + p.radius, fill=p.color_inner, outline="")
self.particle_draw_ids.append((draw_id, None))
def animate_particles(self):
for p, draw_ids in zip(self.particles, self.particle_draw_ids):
p.update(self.w, self.h)
outer_id, inner_id = draw_ids
if inner_id is not None:
self.canvas.coords(outer_id, p.x - p.radius, p.y - p.radius, p.x + p.radius, p.y + p.radius)
r_in = p.radius * 0.45
self.canvas.coords(inner_id, p.x - r_in, p.y - r_in, p.x + r_in, p.y + r_in)
else:
self.canvas.coords(outer_id, p.x - p.radius, p.y - p.radius, p.x + p.radius, p.y + p.radius)
self.root.after(35, self.animate_particles)
def toggle_pause(self):
self.is_paused = not self.is_paused
text = "⏸ PAUZA" if self.is_paused else ""
self.canvas.itemconfig(self.pause_indicator_id, text=text)
def next_product(self):
if self.catalog_items:
self.show_product_by_index(self.current_catalog_index)
self.current_catalog_index = (self.current_catalog_index + 1) % len(self.catalog_items)
def prev_product(self):
if self.catalog_items:
prev_idx = (self.current_catalog_index - 2) % len(self.catalog_items)
self.show_product_by_index(prev_idx)
self.current_catalog_index = (prev_idx + 1) % len(self.catalog_items)
def reload_catalog_action(self):
self.load_catalog()
if self.catalog_items:
self.show_product_by_index(0)
self.current_catalog_index = 1 % len(self.catalog_items)
def update_card_background(self, rgba_color):
self.current_card_rgba = rgba_color
img = Image.new("RGBA", (self.card_w, self.card_h), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
r, g, b, a = rgba_color
border_color = (min(r + 80, 255), min(g + 80, 255), min(b + 80, 255), 200)
draw.rounded_rectangle([0, 0, self.card_w - 1, self.card_h - 1], radius=16, fill=rgba_color, outline=border_color, width=2)
self.card_bg_photo = ImageTk.PhotoImage(img)
self.canvas.itemconfig(self.card_bg_id, image=self.card_bg_photo)
def load_catalog(self):
global KATALOG_DIR
if not os.path.exists(KATALOG_DIR):
KATALOG_DIR = os.path.join(SCRIPT_DIR, "katalog")
if not os.path.exists(KATALOG_DIR):
KATALOG_DIR = os.path.join(SCRIPT_DIR, "documents", "katalog")
self.catalog_items = []
if not os.path.exists(KATALOG_DIR):
return
valid_extensions = ('.png', '.jpg', '.jpeg', '.webp')
for folder_name in sorted(os.listdir(KATALOG_DIR)):
folder_path = os.path.join(KATALOG_DIR, folder_name)
if os.path.isdir(folder_path):
info_path = os.path.join(folder_path, "info.txt")
title = folder_name
price = ""
badge = ""
if os.path.exists(info_path):
try:
with open(info_path, "r", encoding="utf-8") as f:
lines = [line.strip() for line in f.readlines() if line.strip()]
if len(lines) >= 1:
title = lines[0]
if len(lines) >= 2:
price = lines[1]
if len(lines) >= 3:
badge = lines[2]
except Exception as e:
print(f"Chyba při čtení {info_path}: {e}")
images = [f for f in os.listdir(folder_path) if f.lower().endswith(valid_extensions)]
images.sort(key=natural_sort_key)
image_paths = [os.path.join(folder_path, img) for img in images]
self.catalog_items.append({
"title": title,
"price": price,
"badge": badge,
"image_paths": image_paths
})
def cycle_product(self):
if not self.is_paused:
if not self.catalog_items:
self.load_catalog()
if self.catalog_items:
self.show_product_by_index(self.current_catalog_index)
self.current_catalog_index = (self.current_catalog_index + 1) % len(self.catalog_items)
self.root.after(CYCLE_INTERVAL_MS, self.cycle_product)
def show_product_by_index(self, index):
if not self.catalog_items:
return
idx = index % len(self.catalog_items)
self.active_item = self.catalog_items[idx]
self.canvas.itemconfig(self.title_text_id, text=self.active_item["title"])
self.canvas.itemconfig(self.title_shadow_id, text=self.active_item["title"])
price_val = self.active_item["price"]
self.canvas.itemconfig(self.price_text_id, text=price_val)
self.canvas.itemconfig(self.price_shadow_id, text=price_val)
if price_val:
self.canvas.itemconfig(self.price_bg_id, state="normal")
self.canvas.itemconfig(self.price_shadow_id, state="normal")
self.canvas.itemconfig(self.price_text_id, state="normal")
else:
self.canvas.itemconfig(self.price_bg_id, state="hidden")
self.canvas.itemconfig(self.price_shadow_id, state="hidden")
self.canvas.itemconfig(self.price_text_id, state="hidden")
badge_val = self.active_item["badge"]
if badge_val:
badge_upper = badge_val.upper()
if any(w in badge_upper for w in ["VYPRODÁNO", "NENÍ", "OUT"]):
bg_color, text_color = "#e63946", "#ffffff"
elif any(w in badge_upper for w in ["AKCE", "SLEVA", "SALE", "%"]):
bg_color, text_color = "#f77f00", "#ffffff"
elif any(w in badge_upper for w in ["NOVINKA", "NEW", "TOP"]):
bg_color, text_color = "#2a9d8f", "#ffffff"
else:
bg_color, text_color = "#7209b7", "#ffffff"
self.canvas.itemconfig(self.badge_text_id, text=f" {badge_val.upper()} ", fill=text_color, state="normal")
badge_y = self.h * 0.14
self.canvas.coords(self.badge_text_id, self.w * 0.5, badge_y)
bbox = self.canvas.bbox(self.badge_text_id)
if bbox:
x1, y1, x2, y2 = bbox
self.canvas.coords(self.badge_bg_id, x1 - 12, y1 - 5, x2 + 12, y2 + 5)
self.canvas.itemconfig(self.badge_bg_id, fill=bg_color, outline="#ffffff", width=1, state="normal")
else:
self.canvas.itemconfig(self.badge_text_id, state="hidden")
self.canvas.itemconfig(self.badge_bg_id, state="hidden")
self.current_frame_index = 0
self.update_product_frame()
def animate_product_3d(self):
if self.active_item:
paths = self.active_item["image_paths"]
if len(paths) > 1:
self.current_frame_index = (self.current_frame_index + 1) % len(paths)
self.update_product_frame()
self.root.after(FRAME_INTERVAL_MS, self.animate_product_3d)
def update_product_frame(self):
if not self.active_item:
return
paths = self.active_item["image_paths"]
if paths:
img_path = paths[self.current_frame_index % len(paths)]
try:
raw_img = Image.open(img_path).convert("RGBA")
max_w = int(self.w * 0.42)
max_h = int(self.h * 0.52)
raw_img.thumbnail((max_w, max_h), Image.Resampling.LANCZOS)
self.current_prod_photo = ImageTk.PhotoImage(raw_img)
self.canvas.itemconfig(self.prod_image_id, image=self.current_prod_photo)
except Exception as e:
print(f"Chyba při načítání snímku {img_path}: {e}")
self.canvas.itemconfig(self.prod_image_id, image="")
else:
self.canvas.itemconfig(self.prod_image_id, image="")
def poll_data(self):
pythoncom.CoInitialize()
while self.running:
try:
res = requests.get(SERVER_URL, timeout=1)
if res.status_code == 200:
data = res.json()
artist = str(data.get("artist", "")).strip()
title = str(data.get("title", "")).strip()
progress = str(data.get("progressTime", "0:00/0:00"))
img_url = data.get("coverImg", None)
is_ad_flag = bool(data.get("isAd", False) or data.get("is_ad", False))
# Sloučíme VŠECHNY hodnoty z JSONu do jednoho řetězce (pro jistotu, kdyby rozšíření poslalo text v jiném poli)
all_text_values = " ".join([str(v).lower() for v in data.values()])
# Kompletní a rozšířený seznam frází pro spolehlivou detekci reklam
ad_phrases = [
"advertisement", "reklama", "přetvoř svůj poslech",
"pretvor svoj poslech", "tvoje uši si zaslouží",
"spotify premium", "sponzorované", "sponzorovano",
"spotify partner", "partner", "commercial", "spot",
"poslech", "hudba", "atmosféra", "atmosfera",
"1 z 2", "2 z 2", "1 z 1", "2 z 1", "zaslouží",
"zaslouzi", "premium", "propagace", "z 2", "z 1"
]
# Detekce shody buď podle frází kdekoli v datech, nebo podle speciálního vzoru Spotify + čítač (např. 2 z 2)
matched_phrase = next((p for p in ad_phrases if p in all_text_values), None)
is_spotify_ad_pattern = "spotify" in all_text_values and ("z 2" in all_text_values or "z 1" in all_text_values or "reklama" in all_text_values)
is_ad_detected = is_ad_flag or (matched_phrase is not None) or is_spotify_ad_pattern
# Debug výpis do konzole, abyste viděl, co přesně přišlo
if is_ad_detected:
print(f"[REKLAMA DETEKOVÁNA] Zastavuji zvuk. (Shoda s frází: '{matched_phrase}' / vzor Spotify | Všechna data: {all_text_values})")
if not self.is_ad_active:
self.is_ad_active = True
self.trigger_ad_sound()
else:
if self.is_ad_active:
print(f"[KONEC REKLAMY] Vracím hlasitost. | Všechna data: {all_text_values}")
self.is_ad_active = False
self.stop_ad_sound()
self.root.after(0, self.update_gui, artist, title, progress, img_url, self.is_ad_active)
except Exception:
pass
time.sleep(0.4)
def parse_time_to_seconds(self, time_str):
try:
parts = time_str.split(':')
return int(parts[0]) * 60 + int(parts[1])
except Exception:
return 0
def update_gui(self, artist, title, progress, img_url, is_ad=False):
if is_ad:
title_disp = "📢 REKLAMA"
artist_disp = "Spotify Partner"
else:
title_disp = title if len(title) <= 22 else title[:20] + "..."
artist_disp = artist if len(artist) <= 26 else artist[:24] + "..."
self.canvas.itemconfig(self.spotify_title_id, text=title_disp)
self.canvas.itemconfig(self.spotify_subtitle_id, text=artist_disp)
if "/" in progress:
curr, dur = progress.split("/")
self.canvas.itemconfig(self.time_curr_id, text=curr)
self.canvas.itemconfig(self.time_total_id, text=dur)
c_sec = self.parse_time_to_seconds(curr)
d_sec = self.parse_time_to_seconds(dur)
percent = (c_sec / d_sec) if d_sec > 0 else 0
text_x = self.card_x1 + int(self.card_h * 0.82)
bar_x1 = text_x
bar_x2 = self.card_x1 + self.card_w - int(self.card_h * 0.12)
bar_y = self.card_y1 + int(self.card_h * 0.68)
max_w = bar_x2 - bar_x1
fill_x = bar_x1 + int(max_w * percent)
self.canvas.coords(self.pbar_fg, bar_x1, bar_y, fill_x, bar_y)
if img_url and img_url != self.current_img_url:
self.current_img_url = img_url
threading.Thread(target=self.download_cover, args=(img_url,), daemon=True).start()
def download_cover(self, url):
try:
res = requests.get(url, timeout=3)
if res.status_code == 200:
self.raw_cover_img = Image.open(BytesIO(res.content)).convert("RGBA")
small_img = self.raw_cover_img.resize((1, 1), Image.Resampling.BILINEAR)
dom_color = small_img.getpixel((0, 0))
dark_r = int(dom_color[0] * 0.35)
dark_g = int(dom_color[1] * 0.35)
dark_b = int(dom_color[2] * 0.35)
card_rgba = (dark_r, dark_g, dark_b, 225)
cover_size = int(self.card_h * 0.58)
cover_img = self.raw_cover_img.resize((cover_size, cover_size), Image.Resampling.LANCZOS)
self.photo_cover = ImageTk.PhotoImage(cover_img)
self.root.after(0, lambda: [
self.canvas.itemconfig(self.cover_img_id, image=self.photo_cover),
self.update_card_background(card_rgba)
])
except Exception as e:
print(f"Chyba při načítání obalu alba: {e}")
if __name__ == "__main__":
root = tk.Tk()
app = FullscreenJarmarkDisplay(root)
root.mainloop() root.mainloop()