Aktualizovat spotify_widget.py

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

View File

@@ -1,14 +1,53 @@
import tkinter as tk
import requests
import threading
import time
import os import os
import re
import sys
import time
import math
import random import random
import datetime
import threading
import subprocess
import requests
from io import BytesIO from io import BytesIO
from PIL import Image, ImageTk import tkinter as tk
from PIL import Image, ImageTk, ImageDraw
import pythoncom
# Pokus o automatickou instalaci pycaw, pokud chybí
try:
from pycaw.pycaw import AudioUtilities
HAS_PYCAW = True
except ImportError:
print("📦 Instaluji potřebnou knihovnu 'pycaw' pro okamžité ztlumení Windows...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "pycaw", "comtypes"],
creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
from pycaw.pycaw import AudioUtilities
HAS_PYCAW = True
print("✅ Knihovna pycaw byla úspěšně nainstalována!")
except Exception as e:
print(f"⚠️ Automatická instalace pycaw se nezdařila: {e}")
HAS_PYCAW = False
# Pokus o import winsound (standardně na Windows)
try:
import winsound
HAS_WINSOUND = True
except ImportError:
HAS_WINSOUND = False
# Pokus o import pygame
try:
import pygame
pygame.mixer.init()
HAS_PYGAME = True
except Exception:
HAS_PYGAME = False
SERVER_URL = "http://localhost:8080/endpoint" SERVER_URL = "http://localhost:8080/endpoint"
CYCLE_INTERVAL_MS = 10000 # Změna produktu každých 10 sekund CYCLE_INTERVAL_MS = 10000 # Doba zobrazení produktu (10 sekund)
FRAME_INTERVAL_MS = 120 # Rychlost 3D rotace fotek (120 ms)
PARTICLE_COUNT = 60 # Počet bokeh částic
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -16,94 +55,403 @@ KATALOG_DIR = os.path.join(SCRIPT_DIR, "katalog")
if not os.path.exists(KATALOG_DIR): if not os.path.exists(KATALOG_DIR):
KATALOG_DIR = os.path.join(SCRIPT_DIR, "documents", "katalog") KATALOG_DIR = os.path.join(SCRIPT_DIR, "documents", "katalog")
# Složka se zvuky pro reklamy
SOUNDS_DIR = os.path.join(SCRIPT_DIR, "zvuky_reklama")
if not os.path.exists(SOUNDS_DIR):
os.makedirs(SOUNDS_DIR, exist_ok=True)
def natural_sort_key(s):
""" Řazení souborů podle čísel (image1, image2, ..., image10) """
return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', s)]
def set_spotify_mute(mute=True):
""" Bleskově ztlumí VŠECHNY aplikace ve Windows kromě samotného Pythonu """
pythoncom.CoInitialize()
if HAS_PYCAW:
try:
sessions = AudioUtilities.GetAllSessions()
for session in sessions:
try:
pname = session.Process.name().lower() if session.Process else ""
if not any(keep in pname for keep in ["python", "wmplayer"]):
volume = session.SimpleAudioVolume
volume.SetMute(1 if mute else 0, None)
except Exception:
pass
return
except Exception as e:
print(f"Pycaw chyba: {e}")
nircmd_path = os.path.join(SCRIPT_DIR, "nircmd.exe")
if os.path.exists(nircmd_path):
try:
val = "1" if mute else "0"
for proc in ["spotify.exe", "chrome.exe", "msedge.exe", "brave.exe", "firefox.exe", "opera.exe"]:
subprocess.run(
[nircmd_path, "muteappvolume", proc, val],
creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0)
)
except Exception as e:
print(f"NirCmd chyba: {e}")
class AdvancedParticle:
""" Třída pro vícevrstvé animované bokeh částice """
def __init__(self, w=1920, h=1080):
self.w = w
self.h = h
self.reset(random_y=True)
def reset(self, random_y=False):
self.x = random.uniform(0, self.w)
self.y = random.uniform(0, self.h) if random_y else self.h + random.uniform(20, 80)
self.p_type = random.choice(['bokeh_large', 'bokeh_medium', 'spark'])
if self.p_type == 'bokeh_large':
self.base_radius = random.uniform(40, 90)
self.speed_y = random.uniform(0.2, 0.6)
self.sway_amp = random.uniform(1.0, 3.0)
self.sway_freq = random.uniform(0.005, 0.015)
self.color_outer = random.choice(["#2a0845", "#3c096c", "#1e0034", "#480ca8", "#38040e"])
self.color_inner = random.choice(["#5a189a", "#7b2cbf", "#9d4edd", "#7209b7", "#9e2a2b"])
elif self.p_type == 'bokeh_medium':
self.base_radius = random.uniform(18, 38)
self.speed_y = random.uniform(0.5, 1.2)
self.sway_amp = random.uniform(1.5, 4.0)
self.sway_freq = random.uniform(0.01, 0.025)
self.color_outer = random.choice(["#5a189a", "#7209b7", "#b5179e", "#480ca8"])
self.color_inner = random.choice(["#e0aaff", "#f72585", "#c77dff", "#ffb703"])
else: # spark
self.base_radius = random.uniform(2, 5)
self.speed_y = random.uniform(0.8, 2.2)
self.sway_amp = random.uniform(0.5, 2.0)
self.sway_freq = random.uniform(0.02, 0.05)
self.color_outer = "#ffffff"
self.color_inner = random.choice(["#ffffff", "#ffee83", "#4cc9f0", "#ffb703"])
self.radius = self.base_radius
self.pulse_phase = random.uniform(0, 2 * math.pi)
self.pulse_speed = random.uniform(0.015, 0.04)
self.phase = random.uniform(0, 2 * math.pi)
def update(self, w, h):
self.w = w
self.h = h
self.y -= self.speed_y
self.phase += self.sway_freq
self.x += math.sin(self.phase) * self.sway_amp
self.pulse_phase += self.pulse_speed
pulse_factor = 1.0 + 0.22 * math.sin(self.pulse_phase)
self.radius = self.base_radius * pulse_factor
if self.y < -self.radius * 2 or self.x < -self.radius or self.x > self.w + self.radius:
self.reset(random_y=False)
class FullscreenJarmarkDisplay: class FullscreenJarmarkDisplay:
def __init__(self, root): def __init__(self, root):
self.root = root self.root = root
self.root.title("RP + 1602 Display Jarmark") self.root.title("Jarmark Display Kiosk")
self.root.attributes("-fullscreen", True) self.root.attributes("-fullscreen", True)
self.root.bind("<Escape>", lambda e: self.root.destroy())
self.canvas = tk.Canvas(root, width=1920, height=1080, bg="#0d031b", highlightthickness=0) self.w = 1920
self.h = 1080
# Klávesové zkratky
self.root.bind("<Escape>", lambda e: self.root.destroy())
self.root.bind("<Right>", lambda e: self.next_product())
self.root.bind("<Left>", lambda e: self.prev_product())
self.root.bind("<space>", lambda e: self.toggle_pause())
self.root.bind("<r>", lambda e: self.reload_catalog_action())
self.root.bind("<R>", lambda e: self.reload_catalog_action())
# Plátno s čistým tmavým pozadím
self.canvas = tk.Canvas(root, bg="#07010f", highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True) self.canvas.pack(fill=tk.BOTH, expand=True)
self.load_background() self.canvas.bind("<Configure>", self.on_resize)
# Název produktu nahoře # Částice
self.title_text_id = self.canvas.create_text(960, 120, text="", fill="white", font=("Impact", 75, "bold")) self.particles = [AdvancedParticle(self.w, self.h) for _ in range(PARTICLE_COUNT)]
self.particle_draw_ids = []
self.init_particles_canvas()
# Obrázek produktu uprostřed # Canvas Prvky - Název produktu se stínem pro skvělou čitelnost bez rámčku
self.prod_image_id = self.canvas.create_image(920, 510, anchor="center") self.title_shadow_id = self.canvas.create_text(0, 0, text="", fill="#000000", font=("Impact", 42, "bold"), anchor="center")
self.title_text_id = self.canvas.create_text(0, 0, text="", fill="#ffffff", font=("Impact", 42, "bold"), anchor="center")
# --- DESIGN CENOVKY (Vpravo) --- self.badge_bg_id = self.canvas.create_rectangle(0, 0, 0, 0, fill="", outline="", state="hidden")
# 1. Podkladový badge/karta pro cenu self.badge_text_id = self.canvas.create_text(0, 0, text="", fill="white", font=("Segoe UI", 13, "bold"), state="hidden")
self.price_bg_id = self.canvas.create_rectangle(1380, 450, 1840, 570, fill="#150826", outline="#9d4edd", width=3)
# 2. Stín ceny (3D efekt) # Produkt fotka (zcela bez rámečku, volně na pozadí)
self.price_shadow_id = self.canvas.create_text(1613, 513, text="", fill="#000000", font=("Trebuchet MS", 62, "bold"), anchor="center") self.prod_image_id = self.canvas.create_image(0, 0, anchor="center")
# 3. Hlavní text ceny (Zářivě zlatá) # Cenovka vpravo
self.price_text_id = self.canvas.create_text(1610, 510, text="", fill="#FFD700", font=("Trebuchet MS", 62, "bold"), anchor="center") self.price_bg_id = self.canvas.create_rectangle(0, 0, 0, 0, fill="#120521", outline="#9d4edd", width=2)
self.price_shadow_id = self.canvas.create_text(0, 0, text="", fill="#000000", font=("Segoe UI", 38, "bold"), anchor="center")
self.price_text_id = self.canvas.create_text(0, 0, text="", fill="#ffb703", font=("Segoe UI", 38, "bold"), anchor="center")
# Hodiny & Datum
self.clock_time_id = self.canvas.create_text(0, 0, text="", fill="#ffffff", font=("Segoe UI", 26, "bold"), anchor="e")
self.clock_date_id = self.canvas.create_text(0, 0, text="", fill="#9d4edd", font=("Segoe UI", 13, "bold"), anchor="e")
# Spodní lišta přehrávače # Indikátor pauzy
self.canvas.create_rectangle(0, 910, 1920, 1080, fill="#000000", outline="") self.pause_indicator_id = self.canvas.create_text(0, 0, text="", fill="#ff0055", font=("Segoe UI", 13, "bold"), anchor="w")
self.cover_img_id = self.canvas.create_image(80, 995, anchor="center") # Spotify Karta (ovládaná vaším rozšířením, vlevo dole)
self.card_x1, self.card_y1 = 0, 0
self.card_w, self.card_h = 360, 150
self.card_bg_photo = None
self.card_bg_id = self.canvas.create_image(0, 0, anchor="nw")
self.spotify_title_id = self.canvas.create_text(160, 955, text="Načítám...", fill="white", font=("Arial", 22, "bold"), anchor="w") self.cover_img_id = self.canvas.create_image(0, 0, anchor="center")
self.spotify_subtitle_id = self.canvas.create_text(160, 990, text="Hudobné video • Čekám na Spotify", fill="#bbbbbb", font=("Arial", 16), anchor="w") self.spotify_title_id = self.canvas.create_text(0, 0, text="Načítám...", fill="white", font=("Segoe UI", 13, "bold"), anchor="w")
self.spotify_subtitle_id = self.canvas.create_text(0, 0, text="Čekám na Spotify", fill="#c77dff", font=("Segoe UI", 10), anchor="w")
self.time_curr_id = self.canvas.create_text(160, 1040, text="0:00", fill="#aaaaaa", font=("Arial", 14), anchor="w") self.time_curr_id = self.canvas.create_text(0, 0, text="0:00", fill="#aaaaaa", font=("Segoe UI", 9), anchor="w")
self.time_total_id = self.canvas.create_text(1860, 1040, text="0:00", fill="#aaaaaa", font=("Arial", 14), anchor="e") self.time_total_id = self.canvas.create_text(0, 0, text="0:00", fill="#aaaaaa", font=("Segoe UI", 9), anchor="e")
self.pbar_bg = self.canvas.create_line(220, 1040, 1800, 1040, fill="#444444", width=5) self.pbar_bg = self.canvas.create_line(0, 0, 0, 0, fill="#3c096c", width=5, capstyle=tk.ROUND)
self.pbar_fg = self.canvas.create_line(220, 1040, 220, 1040, fill="#ffffff", width=5) self.pbar_fg = self.canvas.create_line(0, 0, 0, 0, fill="#f72585", width=5, capstyle=tk.ROUND)
# Stavové proměnné
self.running = True self.running = True
self.is_paused = False
self.is_ad_active = False
self.ad_sound_process = None
self.current_img_url = None self.current_img_url = None
self.photo_cover = None self.photo_cover = None
self.raw_cover_img = None
self.current_card_rgba = (25, 10, 40, 230)
self.current_prod_photo = None self.current_prod_photo = None
self.catalog_items = [] self.catalog_items = []
self.active_item = None
self.current_catalog_index = 0 self.current_catalog_index = 0
self.current_frame_index = 0
# Spuštění procesů
self.load_catalog() self.load_catalog()
self.cycle_product() self.cycle_product()
self.animate_product_3d()
self.animate_particles()
self.update_clock()
threading.Thread(target=self.poll_data, daemon=True).start() threading.Thread(target=self.poll_data, daemon=True).start()
def load_background(self): def trigger_ad_sound(self):
bg_candidates = [ print("🔇 Ztlumuji vše kromě Pythonu...")
"backgroung.png", set_spotify_mute(True)
"background.png",
"background.jpg",
"backgroung.jpg"
]
bg_path = None if not os.path.exists(SOUNDS_DIR):
for filename in bg_candidates: return
full_path = os.path.join(SCRIPT_DIR, filename)
if os.path.exists(full_path):
bg_path = full_path
break
if bg_path: 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: try:
bg_img = Image.open(bg_path).convert("RGBA").resize((1920, 1080), Image.Resampling.LANCZOS) if HAS_PYGAME:
self.bg_photo = ImageTk.PhotoImage(bg_img) pygame.mixer.music.load(chosen_sound)
self.canvas.create_image(0, 0, image=self.bg_photo, anchor="nw") pygame.mixer.music.play()
except Exception as e: elif chosen_sound.lower().endswith('.wav') and HAS_WINSOUND:
print(f"Chyba při načítání pozadí {bg_path}: {e}") winsound.PlaySound(chosen_sound, winsound.SND_FILENAME | winsound.SND_ASYNC)
else: else:
colors = ["#2b0845", "#410b66", "#581287", "#731aa8", "#8b24ca"] ps_script = f"$m = New-Object -ComObject WMPlayer.OCX; $m.URL = '{chosen_sound}'; $m.controls.play(); Start-Sleep -s 60"
random.seed(42) self.ad_sound_process = subprocess.Popen(
for _ in range(40): ["powershell", "-Command", ps_script],
x = random.randint(0, 1920) creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0)
y = random.randint(0, 1080) )
r = random.randint(40, 150) except Exception as e:
c = random.choice(colors) print(f"Chyba při přehrávání zvuku: {e}")
self.canvas.create_oval(x-r, y-r, x+r, y+r, fill=c, outline="")
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): def load_catalog(self):
global KATALOG_DIR global KATALOG_DIR
@@ -114,17 +462,18 @@ class FullscreenJarmarkDisplay:
self.catalog_items = [] self.catalog_items = []
if not os.path.exists(KATALOG_DIR): if not os.path.exists(KATALOG_DIR):
print(f"Adresář 'katalog' neexistuje na cestě: {KATALOG_DIR}")
return return
valid_extensions = ('.png', '.jpg', '.jpeg', '.webp')
for folder_name in sorted(os.listdir(KATALOG_DIR)): for folder_name in sorted(os.listdir(KATALOG_DIR)):
folder_path = os.path.join(KATALOG_DIR, folder_name) folder_path = os.path.join(KATALOG_DIR, folder_name)
if os.path.isdir(folder_path): if os.path.isdir(folder_path):
info_path = os.path.join(folder_path, "info.txt") info_path = os.path.join(folder_path, "info.txt")
image_path = os.path.join(folder_path, "image.png")
title = folder_name title = folder_name
price = "" price = ""
badge = ""
if os.path.exists(info_path): if os.path.exists(info_path):
try: try:
@@ -134,67 +483,164 @@ class FullscreenJarmarkDisplay:
title = lines[0] title = lines[0]
if len(lines) >= 2: if len(lines) >= 2:
price = lines[1] price = lines[1]
if len(lines) >= 3:
badge = lines[2]
except Exception as e: except Exception as e:
print(f"Chyba při čtení {info_path}: {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({ self.catalog_items.append({
"title": title, "title": title,
"price": price, "price": price,
"image_path": image_path if os.path.exists(image_path) else None "badge": badge,
"image_paths": image_paths
}) })
def cycle_product(self): def cycle_product(self):
if self.current_catalog_index == 0: if not self.is_paused:
if not self.catalog_items:
self.load_catalog() self.load_catalog()
if self.catalog_items: if self.catalog_items:
self.current_catalog_index %= len(self.catalog_items) self.show_product_by_index(self.current_catalog_index)
item = self.catalog_items[self.current_catalog_index]
self.canvas.itemconfig(self.title_text_id, text=item["title"])
# Nastavení ceny i stínu ceny
price_val = item["price"]
self.canvas.itemconfig(self.price_text_id, text=price_val)
self.canvas.itemconfig(self.price_shadow_id, text=price_val)
# Pokud cenovka nemá text, schováme podkladový obdélník
if price_val:
self.canvas.itemconfig(self.price_bg_id, state="normal")
else:
self.canvas.itemconfig(self.price_bg_id, state="hidden")
if item["image_path"]:
try:
raw_img = Image.open(item["image_path"]).convert("RGBA")
raw_img.thumbnail((700, 460), 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í obrázku {item['image_path']}: {e}")
self.canvas.itemconfig(self.prod_image_id, image="")
else:
self.canvas.itemconfig(self.prod_image_id, image="")
self.current_catalog_index = (self.current_catalog_index + 1) % len(self.catalog_items) self.current_catalog_index = (self.current_catalog_index + 1) % len(self.catalog_items)
self.root.after(CYCLE_INTERVAL_MS, self.cycle_product) 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): def poll_data(self):
pythoncom.CoInitialize()
while self.running: while self.running:
try: try:
res = requests.get(SERVER_URL, timeout=1) res = requests.get(SERVER_URL, timeout=1)
if res.status_code == 200: if res.status_code == 200:
data = res.json() data = res.json()
artist = data.get("artist", "Neznámý interpret")
title = data.get("title", "Čekám na skladbu")
progress = data.get("progressTime", "0:00/0:00")
img_url = data.get("coverImg", None)
self.root.after(0, self.update_gui, artist, title, progress, img_url) 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: except Exception:
pass pass
time.sleep(0.8) time.sleep(0.4)
def parse_time_to_seconds(self, time_str): def parse_time_to_seconds(self, time_str):
try: try:
@@ -203,9 +649,16 @@ class FullscreenJarmarkDisplay:
except Exception: except Exception:
return 0 return 0
def update_gui(self, artist, title, progress, img_url): def update_gui(self, artist, title, progress, img_url, is_ad=False):
self.canvas.itemconfig(self.spotify_title_id, text=title) if is_ad:
self.canvas.itemconfig(self.spotify_subtitle_id, text=f"Hudobné video • {artist}") 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: if "/" in progress:
curr, dur = progress.split("/") curr, dur = progress.split("/")
@@ -216,10 +669,14 @@ class FullscreenJarmarkDisplay:
d_sec = self.parse_time_to_seconds(dur) d_sec = self.parse_time_to_seconds(dur)
percent = (c_sec / d_sec) if d_sec > 0 else 0 percent = (c_sec / d_sec) if d_sec > 0 else 0
start_x = 220 text_x = self.card_x1 + int(self.card_h * 0.82)
max_w = 1800 - start_x bar_x1 = text_x
fill_x = start_x + int(max_w * percent) bar_x2 = self.card_x1 + self.card_w - int(self.card_h * 0.12)
self.canvas.coords(self.pbar_fg, start_x, 1040, fill_x, 1040) 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: if img_url and img_url != self.current_img_url:
self.current_img_url = img_url self.current_img_url = img_url
@@ -229,13 +686,27 @@ class FullscreenJarmarkDisplay:
try: try:
res = requests.get(url, timeout=3) res = requests.get(url, timeout=3)
if res.status_code == 200: if res.status_code == 200:
raw_img = Image.open(BytesIO(res.content)).convert("RGBA") self.raw_cover_img = Image.open(BytesIO(res.content)).convert("RGBA")
raw_img = raw_img.resize((100, 100), Image.Resampling.LANCZOS)
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}")
self.photo_cover = ImageTk.PhotoImage(raw_img)
self.root.after(0, lambda: self.canvas.itemconfig(self.cover_img_id, image=self.photo_cover))
except Exception:
pass
if __name__ == "__main__": if __name__ == "__main__":
root = tk.Tk() root = tk.Tk()