Začetna verzija (avtor U. Urbanija)

This commit is contained in:
2026-03-04 20:18:24 +01:00
commit 79fa50fae4
4 changed files with 1231 additions and 0 deletions

417
add_song.py Normal file
View File

@@ -0,0 +1,417 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sqlite3
import json
import os
DB_PATH = 'songs.db'
SETTINGS_PATH = 'settings.json'
EXPORT_PATH = 'pesmi_export.txt'
# ---------------------------------------------------------------------------
# PREDHODNE NASTAVITVE IN POVEZAVA NA BAZO
# ---------------------------------------------------------------------------
if not os.path.exists(SETTINGS_PATH):
print("Nastavitve manjkajo! Zaženi 'nastavitve.bat' najprej.")
exit()
with open(SETTINGS_PATH, "r", encoding="utf-8") as f:
settings = json.load(f)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS songs (
id INTEGER PRIMARY KEY,
title TEXT,
lyrics TEXT
)''')
conn.commit()
# ---------------------------------------------------------------------------
# POMOŽNE FUNKCIJE
# ---------------------------------------------------------------------------
def get_next_free_id(start: int = 792) -> int:
"""Vrne prvo prosto številko pesmi, ki je ≥ start (privzeto 792)."""
next_id = start
while True:
cursor.execute("SELECT 1 FROM songs WHERE id = ?", (next_id,))
if cursor.fetchone() is None:
return next_id
next_id += 1
def slovenski_kljuc(naslov: str) -> str:
nadom = {'č': 'cz', 'Č': 'Cz', 'š': 'sz', 'Š': 'Sz', 'ž': 'zz', 'Ž': 'Zz'}
return ''.join(nadom.get(z, z) for z in naslov).lower()
def prikazi_meni():
print("\nIzberi možnost:")
print("1. Dodaj pesem")
print("2. Uredi pesem")
print("3. Prestavi pesem")
print("4. Izbriši pesem")
print("5. Izvozi bazo v TXT")
print("6. Uvozi bazo iz TXT")
print("7. Izpiši kazalo")
print("8. Zaženi projekcijo")
print("9. Zapri program")
def uredi_vnos(lines):
processed, prazne = [], 0
for line in lines:
if line.strip() == "":
prazne += 1
continue
if prazne:
processed.extend([""] * prazne)
prazne = 0
if line.strip().startswith("- "):
line = line.replace("- ", "", 1)
processed.append(line)
if prazne:
processed.extend([""] * prazne)
return "\n".join(processed)
# ---------------------------------------------------------------------------
# FUNKCIJE POSAMEZNIH MENIJSKIH OPCIJ
# ---------------------------------------------------------------------------
def dodaj_pesem():
# -- izbira št. pesmi ----------------------------------------------------
while True:
try:
song_id = int(input("Vnesi številko pesmi: "))
break
except ValueError:
print("Vnesi veljavno celo število!")
cursor.execute("SELECT title FROM songs WHERE id = ?", (song_id,))
row = cursor.fetchone()
if row:
print(f"Želiš prepisati obstoječo pesem {song_id} {row[0]}?")
predlagana = get_next_free_id()
print(f"Prva prosta številka nad 791 je {predlagana}.")
izbira = input("D = prepiši, N ali Enter = uporabi predlagano, "
"ali vnesi drugo številko: ").strip()
if izbira.lower() == "d":
pass
elif izbira.lower() == "n" or izbira == "":
song_id = predlagana
else:
try:
song_id = int(izbira)
except ValueError:
print("Neveljaven vnos uporabljam predlagano številko.")
song_id = predlagana
# -- vnos besedila -------------------------------------------------------
title = input("Vnesi naslov pesmi: ")
print("Vnesi besedilo (prazna vrstica = presledek, dve prazni = nova kitica).\n"
"Ko končaš, vpiši 'konec'.")
lines = []
while True:
line = input()
if line.strip().lower() == "konec":
break
if line.strip().startswith("- "):
line = line.replace("- ", "", 1)
lines.append(line)
full_lyrics = uredi_vnos(lines)
cursor.execute("INSERT OR REPLACE INTO songs (id, title, lyrics) VALUES (?,?,?)",
(song_id, title, full_lyrics))
conn.commit()
print("Pesem uspešno shranjena!")
def uredi_pesem():
try:
song_id = int(input("Vnesi številko pesmi za urejanje: "))
except ValueError:
print("Uporabi celo število!")
return
cursor.execute("SELECT title, lyrics FROM songs WHERE id = ?", (song_id,))
result = cursor.fetchone()
if not result:
print("Pesem s to številko ne obstaja.")
return
print(f"Trenutni naslov: {result[0]}")
print(f"Trenutno besedilo:\n{result[1]}")
new_title = input("Nov naslov (pusti prazno za obdržanje): ")
print("Novo besedilo (vpiši 'konec' za zaključek, pusti prazno za obdržanje):")
lines = []
while True:
line = input()
if line.strip().lower() == "konec":
break
if line.strip().startswith("- "):
line = line.replace("- ", "", 1)
lines.append(line)
if new_title.strip() == "":
new_title = result[0]
new_lyrics = result[1] if len(lines) == 0 else uredi_vnos(lines)
cursor.execute("UPDATE songs SET title = ?, lyrics = ? WHERE id = ?",
(new_title, new_lyrics, song_id))
conn.commit()
print("Pesem uspešno posodobljena!")
def prestavi_pesem():
try:
old_id = int(input("Vnesi številko pesmi, ki jo želiš prestaviti: "))
new_id = int(input("Vnesi novo številko pesmi: "))
except ValueError:
print("Uporabi cela števila!")
return
cursor.execute("SELECT title, lyrics FROM songs WHERE id = ?", (old_id,))
vir = cursor.fetchone()
if vir is None:
print("Pesem s to številko ne obstaja.")
return
cursor.execute("SELECT title FROM songs WHERE id = ?", (new_id,))
cilj = cursor.fetchone()
if cilj:
print(f"Želiš prepisati obstoječo pesem {new_id} {cilj[0]}?")
predlagana = get_next_free_id()
print(f"Prva prosta številka nad 791 je {predlagana}.")
izbira = input("D = prepiši, N ali Enter = predlagana, ali vnesi drugo številko: ").strip()
if izbira.lower() == "d":
pass
elif izbira.lower() == "n" or izbira == "":
new_id = predlagana
else:
try:
new_id = int(izbira)
except ValueError:
print("Neveljaven vnos uporabljam predlagano številko.")
new_id = predlagana
cursor.execute("INSERT OR REPLACE INTO songs (id, title, lyrics) VALUES (?,?,?)",
(new_id, vir[0], vir[1]))
cursor.execute("DELETE FROM songs WHERE id = ?", (old_id,))
conn.commit()
print("Pesem uspešno prestavljena!")
def izbrisi_pesem():
try:
song_id = int(input("Vnesi številko pesmi za izbris: "))
except ValueError:
print("Uporabi celo število!")
return
cursor.execute("SELECT title FROM songs WHERE id = ?", (song_id,))
row = cursor.fetchone()
if row is None:
print("Pesem s to številko ne obstaja.")
return
potrdi = input(f"Želiš res izbrisati pesem {song_id} {row[0]}? (D/N): ").strip().lower()
if potrdi == "d":
cursor.execute("DELETE FROM songs WHERE id = ?", (song_id,))
conn.commit()
print("Pesem uspešno izbrisana!")
else:
print("Brisanje preklicano.")
def izvozi_bazo():
cursor.execute("SELECT id, title, lyrics FROM songs ORDER BY id")
pesmi = cursor.fetchall()
with open(EXPORT_PATH, "w", encoding="utf-8") as f:
for pesem in pesmi:
f.write("===\n")
f.write(f"{pesem[0]}\n{pesem[1]}\n{pesem[2]}\n")
print(f"Baza je bila izvožena v '{EXPORT_PATH}'.")
# ---------------------------------------------------------------------------
# 6. UVOZ BAZE Z NAPREDNO KONTROLO PODVOJENIH PESMI
# ---------------------------------------------------------------------------
def uvozi_bazo():
if not os.path.exists(EXPORT_PATH):
print(f"Datoteka '{EXPORT_PATH}' ne obstaja.")
return
with open(EXPORT_PATH, "r", encoding="utf-8") as f:
vsebina = f.read().replace('\r\n', '\n').replace('\r', '\n')
pesmi_raw = [p for p in vsebina.strip().split("===\n") if p.strip()]
duplicate_mode = None # 'd', 'n', 'p' ==> vedno prepiši / preskoči / nova ID
for pesem_raw in pesmi_raw:
vrstice = pesem_raw.strip().split("\n")
if len(vrstice) < 3:
print("Preskočena nepopolna pesem.")
continue
try:
song_id = int(vrstice[0])
except ValueError:
print("Neveljaven ID preskočeno.")
continue
title = vrstice[1]
raw_lyrics_lines = vrstice[2:]
# -- predobdelava besedila ------------------------------------------
processed_lines, prazne = [], 0
for line in raw_lyrics_lines:
if line.strip().startswith("- "):
line = line.replace("- ", "", 1)
if line.strip() == "":
prazne += 1
continue
if prazne:
processed_lines.extend([""] * prazne)
prazne = 0
processed_lines.append(line)
if prazne:
processed_lines.extend([""] * prazne)
lyrics = "\n".join(processed_lines)
# -- obravnava podvojene številke -----------------------------------
cursor.execute("SELECT title FROM songs WHERE id = ?", (song_id,))
exists = cursor.fetchone()
if exists:
# Če imamo globalni način, ga upoštevamo
if duplicate_mode is not None:
choice = duplicate_mode
else:
print(f"\nPozor: pesem št. {song_id} \"{exists[0]}\" že obstaja.")
print("Izberi dejanje:")
print(" d … prepiši")
print(" n … preskoči")
print(" p … shrani pod prvo prosto številko ≥ 792")
print(" da … prepiši to in vse naslednje podvojene pesmi")
print(" na … preskoči to in vse naslednje podvojene pesmi")
print(" pa … shrani to in vse naslednje podvojene pesmi pod novo ID")
izbira = input("Tvoja izbira: ").strip().lower()
if izbira in {"da", "na", "pa"}:
duplicate_mode = izbira[0] # 'd', 'n', 'p'
choice = duplicate_mode
elif izbira in {"d", "n", "p"}:
choice = izbira
else:
print("Neznana izbira pesem preskočena.")
continue
# izvajanje po izbrani logiki
if choice == "n":
print("Pesem preskočena.")
continue
elif choice == "p":
song_id = get_next_free_id()
print(f"Shranjujem pod novo številko {song_id}.")
# choice == 'd' -> prepis
# -- shranjevanje pesmi ---------------------------------------------
cursor.execute("INSERT OR REPLACE INTO songs (id, title, lyrics) VALUES (?,?,?)",
(song_id, title, lyrics))
conn.commit()
print("\nUvoz pesmi uspešen!")
# ---------------------------------------------------------------------------
# DODATNE FUNKCIJE (kazalo, projekcija) nespremenjene
# ---------------------------------------------------------------------------
def izpisi_kazalo():
poglavja = [
(13, " -- SLAVIMO GOSPODA -- "),
(13, "ADVENTNE"),
(27, "BOŽIČNA DEVETDNEVNICA"),
(39, "BOŽIČNE"),
(65, "POSTNE"),
(119, "VELIKONOČNE"),
(142, "ČAS MED LETOM"),
(160, "MARIJINE"),
(240, "SVETNIŠKE"),
(263, "ZAKRAMENTI"),
(383, "MAŠNE"),
(406, "DAROVANJSKE"),
(419, "OBHAJILNE"),
(470, "BLAGOSLOVNE"),
(497, "ZAKRAMENT SPRAVE"),
(505, "MAŠNIŠKO POSVEČENJE"),
(539, "POGREB"),
(696, "LITANIJE"),
(773, "KRIŽEV POT"),
(777, "ROŽNI VENEC"),
(786, "MISIJONSKE"),
(788, "ZAHVALNE"),
(790, "JUBILATE DEO in ON ŽIVI"),
(1273,"DRUGE PESMI"),
]
cursor.execute("SELECT id, title FROM songs ORDER BY id")
zaporedno = cursor.fetchall()
cursor.execute("SELECT id, title FROM songs")
abecedno = cursor.fetchall()
abecedno.sort(key=lambda x: slovenski_kljuc(x[1]))
with open("kazalo.txt", "w", encoding="utf-8") as f:
f.write("ZAPOREDNO KAZALO\n")
trenutno_poglavje, indeks = None, 0
for pesem in zaporedno:
while indeks < len(poglavja) and pesem[0] >= poglavja[indeks][0]:
trenutno_poglavje = poglavja[indeks][1]
f.write(f"\n--- {trenutno_poglavje} ---\n")
indeks += 1
f.write(f"{pesem[0]}\t{pesem[1]}\n")
f.write("\n\nABECEDNO KAZALO\n")
for pesem in abecedno:
f.write(f"{pesem[0]}\t{pesem[1]}\n")
print("Kazalo izpisano v 'kazalo.txt'.")
def zazeni_projekcijo():
print("Zaganjam projekcijo …")
conn.close()
python_exe = os.sys.executable
os.execl(python_exe, python_exe, "projector.py")
# ---------------------------------------------------------------------------
# G L A V N I Z A G O N
# ---------------------------------------------------------------------------
def main():
while True:
prikazi_meni()
izbira = input("Vnesi številko možnosti: ").strip()
os.system('cls' if os.name == 'nt' else 'clear')
if izbira == "1": dodaj_pesem()
elif izbira == "2": uredi_pesem()
elif izbira == "3": prestavi_pesem()
elif izbira == "4": izbrisi_pesem()
elif izbira == "5": izvozi_bazo()
elif izbira == "6": uvozi_bazo()
elif izbira == "7": izpisi_kazalo()
elif izbira == "8": zazeni_projekcijo()
elif izbira == "9":
print("Program se zapira …")
break
else:
print("Napačna izbira, poskusi znova.")
conn.close()
if __name__ == "__main__":
main()

377
nastavitve.py Normal file
View File

@@ -0,0 +1,377 @@
import tkinter as tk
import json
import os
import subprocess
import sys
DEFAULT_SETTINGS = {
"font_name": "Times New Roman",
"bg_color": "#000000",
"fg_color": "#FFFFFF",
"font_size": 32,
"screen_width_percent": 60,
"font_bold": True,
"show_song_info": True,
"split_by_stanza": True
}
SETTINGS_FILE = "settings.json"
if not os.path.exists(SETTINGS_FILE):
with open(SETTINGS_FILE, "w") as f:
json.dump(DEFAULT_SETTINGS, f, indent=4)
def load_settings():
with open(SETTINGS_FILE, "r") as f:
return json.load(f)
def save_settings(settings):
with open(SETTINGS_FILE, "w") as f:
json.dump(settings, f, indent=4)
def run_menu():
root = tk.Tk()
root.attributes("-fullscreen", True)
root.update_idletasks()
settings = load_settings()
def calculate_max_font_size():
screen_height = root.winfo_screenheight()
screen_width = root.winfo_screenwidth()
max_lines = 12
max_height_per_line = screen_height / max_lines
max_font_size = int(max_height_per_line * 0.8)
return min(max_font_size, settings["font_size"])
FONT_SIZE = calculate_max_font_size()
FONT = (settings["font_name"], FONT_SIZE, "bold" if settings.get("font_bold") else "normal")
root.lift()
root.attributes("-topmost", True)
root.after(100, lambda: root.attributes("-topmost", False))
root.configure(bg=settings["bg_color"])
screen_width = root.winfo_screenwidth()
left_right_width = int((100 - settings["screen_width_percent"]) / 2 * screen_width / 100)
left_block = tk.Frame(root, bg="black", width=left_right_width)
left_block.pack(side="left", fill="y")
right_block = tk.Frame(root, bg="black", width=left_right_width)
right_block.pack(side="right", fill="y")
frame = tk.Frame(root, bg=settings["bg_color"])
frame.pack(expand=True)
def restart_program():
python = sys.executable
subprocess.Popen([python] + sys.argv)
sys.exit()
def update_menu(options, title, callback):
for widget in frame.winfo_children():
widget.destroy()
title_label = tk.Label(frame, text=title, font=FONT, fg=settings["fg_color"], bg=settings["bg_color"])
title_label.pack(pady=(0, 20))
button_frame = tk.Frame(frame, bg=settings["bg_color"])
button_frame.pack()
wrap_length = screen_width * settings["screen_width_percent"] // 100
for key, text in options.items():
label = tk.Label(
button_frame,
text=f"{key}. {text}",
anchor="w",
font=FONT,
fg=settings["fg_color"],
bg=settings["bg_color"],
wraplength=wrap_length
)
label.pack(fill='x', anchor="w")
entry = tk.Entry(frame, font=FONT, justify='center', fg=settings["fg_color"], bg=settings["bg_color"],
insertbackground=settings["fg_color"])
entry.pack(pady=20)
def on_enter(event=None):
choice = entry.get()
callback(choice)
entry.bind("<Return>", on_enter)
root.after(100, entry.focus_set)
def keep_focus(entry_widget):
def refocus():
entry_widget.focus_set()
entry_widget.after(1000, refocus)
refocus()
keep_focus(entry)
def manual_input(prompt, setting_key):
for widget in frame.winfo_children():
widget.destroy()
label = tk.Label(frame, text=prompt, font=FONT, fg=settings["fg_color"], bg=settings["bg_color"])
label.pack(pady=10)
entry = tk.Entry(frame, font=FONT, justify='center', fg=settings["fg_color"], bg=settings["bg_color"],
insertbackground=settings["fg_color"])
entry.pack(pady=20)
def on_enter(event=None):
value = entry.get()
if setting_key in ["font_size", "screen_width_percent"]:
if value.isdigit():
settings[setting_key] = int(value)
else:
settings[setting_key] = value
save_settings(settings)
restart_program()
entry.bind("<Return>", on_enter)
root.after(100, entry.focus_set)
def keep_focus(entry_widget):
def refocus():
entry_widget.focus_set()
entry_widget.after(1000, refocus)
refocus()
keep_focus(entry)
def show_main_menu():
options = {
"1": "Spremeni pisavo",
"2": "Spremeni barvo ozadja",
"3": "Spremeni barvo črk",
"4": "Spremeni barvo ozadja in črk",
"5": "Spremeni velikost črk",
"6": "Spremeni odebeljenost pisave",
"7": "Prikaz številke in kitice",
"8": "Prikaz samostojne kitice",
"9": "Spremeni širino platna",
"10": "Zaženi projekcijo",
"11": "Zapri"
}
def handle_main_choice(choice):
if choice == "1":
show_font_menu()
elif choice == "2":
show_bg_menu()
elif choice == "3":
show_fg_menu()
elif choice == "4":
show_bg_fg_combo_menu()
elif choice == "5":
manual_input("Vnesi velikost pisave (številka):", "font_size")
elif choice == "6":
toggle_bold_choice()
elif choice == "7":
toggle_show_song_info()
elif choice == "8":
toggle_split_by_stanza()
elif choice == "9":
manual_input("Vnesi širino platna (v %):", "screen_width_percent")
elif choice == "10":
root.destroy()
subprocess.run(["python", "projector.py"])
elif choice == "11":
root.destroy()
update_menu(options, "Izberi možnost:", handle_main_choice)
def toggle_bold_choice():
options = {
"1": "Odebeljena pisava (Da)",
"2": "Odebeljena pisava (Ne)"
}
def handle_choice(choice):
if choice == "1":
settings["font_bold"] = True
elif choice == "2":
settings["font_bold"] = False
save_settings(settings)
restart_program()
update_menu(options, "Izberi možnost:", handle_choice)
def toggle_show_song_info():
options = {
"1": "Prikaz številke in kitice (Da)",
"2": "Prikaz številke in kitice (Ne)"
}
def handle_choice(choice):
if choice == "1":
settings["show_song_info"] = True
elif choice == "2":
settings["show_song_info"] = False
save_settings(settings)
restart_program()
update_menu(options, "Izberi možnost:", handle_choice)
def toggle_split_by_stanza():
options = {
"1": "Prelom po kiticah",
"2": "Prelom ob polnem platnu"
}
def handle_choice(choice):
if choice == "1":
settings["split_by_stanza"] = True
elif choice == "2":
settings["split_by_stanza"] = False
save_settings(settings)
restart_program()
update_menu(options, "Izberi način prikaza kitic:", handle_choice)
def show_font_menu():
fonts = {
"1": "Arial",
"2": "Calibri",
"3": "Times New Roman",
"4": "Verdana",
"5": "Georgia",
"6": "Helvetica",
"7": "Tahoma",
"8": "Trebuchet MS",
"9": "Courier New",
"10": "Comic Sans MS"
}
def set_font(choice):
if choice in fonts:
settings["font_name"] = fonts[choice]
save_settings(settings)
restart_program()
update_menu(fonts, "Izberi pisavo:", set_font)
def show_bg_menu():
current_fg = settings["fg_color"]
colors = {
"1": "črna",
"2": "bela",
"3": "rdeča",
"4": "zelena",
"5": "modra",
"6": "rumena",
"7": "cian",
"8": "magenta",
"9": "siva",
"10": "temno rdeča"
}
color_values = {
"črna": "#000000",
"bela": "#FFFFFF",
"rdeča": "#FF0000",
"zelena": "#00FF00",
"modra": "#0000FF",
"rumena": "#FFFF00",
"cian": "#00FFFF",
"magenta": "#FF00FF",
"siva": "#808080",
"temno rdeča": "#800000"
}
filtered_colors = {k: v for k, v in colors.items() if color_values.get(v, "") != current_fg}
def set_bg(choice):
if choice in filtered_colors:
settings["bg_color"] = color_values[filtered_colors[choice]]
save_settings(settings)
restart_program()
update_menu(filtered_colors, "Izberi barvo ozadja:", set_bg)
def show_fg_menu():
current_bg = settings["bg_color"]
colors = {
"1": "bela",
"2": "črna",
"3": "rdeča",
"4": "zelena",
"5": "modra",
"6": "rumena",
"7": "cian",
"8": "magenta",
"9": "siva",
"10": "temno rdeča"
}
color_values = {
"črna": "#000000",
"bela": "#FFFFFF",
"rdeča": "#FF0000",
"zelena": "#00FF00",
"modra": "#0000FF",
"rumena": "#FFFF00",
"cian": "#00FFFF",
"magenta": "#FF00FF",
"siva": "#808080",
"temno rdeča": "#800000"
}
filtered_colors = {k: v for k, v in colors.items() if color_values.get(v, "") != current_bg}
def set_fg(choice):
if choice in filtered_colors:
settings["fg_color"] = color_values[filtered_colors[choice]]
save_settings(settings)
restart_program()
update_menu(filtered_colors, "Izberi barvo črk:", set_fg)
def show_bg_fg_combo_menu():
combinations = {
"1": ("črna", "bela"),
"2": ("bela", "črna"),
"3": ("modra", "rumena"),
"4": ("temno rdeča", "bela"),
"5": ("zelena", "črna"),
"6": ("rdeča", "črna"),
"7": ("rumena", "črna"),
"8": ("črna", "rumena"),
"9": ("modra", "bela"),
"10": ("siva", "črna"),
}
color_values = {
"črna": "#000000",
"bela": "#FFFFFF",
"rdeča": "#FF0000",
"zelena": "#00FF00",
"modra": "#0000FF",
"rumena": "#FFFF00",
"cian": "#00FFFF",
"magenta": "#FF00FF",
"siva": "#808080",
"temno rdeča": "#800000"
}
def handle_combo_choice(choice):
if choice in combinations:
bg_name, fg_name = combinations[choice]
settings["bg_color"] = color_values[bg_name]
settings["fg_color"] = color_values[fg_name]
save_settings(settings)
restart_program()
options = {key: f"{bg} ozadje / {fg} črke" for key, (bg, fg) in combinations.items()}
update_menu(options, "Izberi kombinacijo barv:", handle_combo_choice)
show_main_menu()
root.mainloop()
if __name__ == "__main__":
run_menu()

427
projector.py Normal file
View File

@@ -0,0 +1,427 @@
import sqlite3
import tkinter as tk
import json
import os
import math
import subprocess
DB_PATH = 'songs.db'
SETTINGS_PATH = 'settings.json'
class SongProjector:
def __init__(self, root):
self.root = root
self.conn = sqlite3.connect(DB_PATH)
self.cursor = self.conn.cursor()
self.current_song = None
self.pages = []
self.current_page_index = 0
self.song_number = ""
self.song_number_last = ""
self.all_caps_mode = False
# --------------------------------------------------
# Nastavitve
# --------------------------------------------------
if not os.path.exists(SETTINGS_PATH):
self.settings = {
"font_name": "Times New Roman",
"bg_color": "#000000",
"fg_color": "#FFFFFF",
"font_size": 32,
"screen_width_percent": 60,
"font_bold": True,
"show_song_info": True
}
with open(SETTINGS_PATH, "w", encoding="utf-8") as f:
json.dump(self.settings, f, indent=4)
else:
with open(SETTINGS_PATH, "r", encoding="utf-8") as f:
self.settings = json.load(f)
self.line_height = int(int(self.settings["font_size"]) * 1.5)
# --------------------------------------------------
# Okno
# --------------------------------------------------
root.attributes('-fullscreen', True)
root.configure(bg=self.settings["bg_color"])
root.bind("<Return>", self.enter_pressed)
root.bind("<KP_Enter>", self.enter_pressed)
root.bind("<plus>", self.clear_screen)
root.bind("<minus>", self.prev_page)
root.bind("<slash>", self.search_song)
root.bind("<Key>", self.key_pressed)
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
screen_width_percent = self.settings["screen_width_percent"]
color_width = int(screen_width * screen_width_percent / 100)
self.screen_height = screen_height
self.color_width = color_width
black_side_width = (screen_width - color_width) // 2
self.left_frame = tk.Frame(root, bg="black", width=black_side_width, height=screen_height)
self.left_frame.pack(side="left", fill="y")
self.color_frame = tk.Frame(root, bg=self.settings["bg_color"], width=color_width, height=screen_height)
self.color_frame.place(relx=0.5, rely=0.5, anchor="center")
self.right_frame = tk.Frame(root, bg="black", width=black_side_width, height=screen_height)
self.right_frame.pack(side="right", fill="y")
font_weight = "bold" if self.settings.get("font_bold", False) else "normal"
self.label = tk.Label(
self.color_frame,
text="",
bg=self.settings["bg_color"],
fg=self.settings["fg_color"],
font=(self.settings["font_name"], int(self.settings["font_size"]), font_weight),
wraplength=color_width,
justify="center"
)
self.label.pack(expand=True)
right_edge_x = int((screen_width - color_width) / 2 + color_width)
self.song_info_label = tk.Label(
root,
text="",
bg=self.settings["bg_color"],
fg=self.settings["fg_color"],
font=(self.settings["font_name"], int(self.settings["font_size"]) - 5),
anchor="se",
justify="right"
)
self.song_info_label.place(x=right_edge_x - 10, y=screen_height - 10, anchor="se")
self.search_label = None
self.search_entry = None
self.waiting_for_song = True
# --------------------------------------------------
# Samodejno skrivanje kazalca
# --------------------------------------------------
def hide_cursor_after_delay():
self.root.config(cursor="none")
def reset_cursor_timer(event=None):
self.root.config(cursor="")
if hasattr(self, "cursor_job"):
self.root.after_cancel(self.cursor_job)
self.cursor_job = self.root.after(0, hide_cursor_after_delay)
self.root.bind_all('<Any-Motion>', reset_cursor_timer)
self.root.bind_all('<Key>', reset_cursor_timer)
self.cursor_job = self.root.after(0, hide_cursor_after_delay)
self.clear_screen()
# ------------------------------------------------------
# NOVA METODA: enakomeren prelom predolgih vrstic
# ------------------------------------------------------
def split_long_line(self, line):
"""
Vrne seznam podvrstic, ki skupaj tvorijo `line`,
pri čemer so (približno) enako dolge glede na število znakov.
"""
avg_char_width = int(self.settings["font_size"]) * 0.57
wraplength_px = self.color_width
# Koliko znakov približno gre v eno vrstico
approx_chars_per_line = max(1, int(wraplength_px / avg_char_width))
if len(line) <= approx_chars_per_line:
return [line]
# Potrebno število podvrstic
n_sub = math.ceil(len(line) / approx_chars_per_line)
words = line.split()
total_chars = sum(len(w) for w in words) + len(words) - 1 # vključno s presledki
target_len = total_chars / n_sub
sub_lines = []
current_words = []
current_len = 0
for word in words:
added_len = len(word) + (1 if current_words else 0) # presledek pred besedo, če ni prva
if current_len + added_len > target_len and len(sub_lines) < n_sub - 1:
# Začni novo vrstico
sub_lines.append(" ".join(current_words))
current_words = [word]
current_len = len(word)
else:
current_words.append(word)
current_len += added_len
if current_words:
sub_lines.append(" ".join(current_words))
return sub_lines
# ------------------------------------------------------
# Upravljanje tipkovnice
# ------------------------------------------------------
def key_pressed(self, event):
if event.keysym.isdigit():
self.song_number += event.char
elif event.keysym == "BackSpace":
self.song_number = self.song_number[:-1]
elif event.char == "*":
self.all_caps_mode = not self.all_caps_mode
self.show_page()
def enter_pressed(self, event=None):
if self.song_number:
self.load_song()
elif not self.waiting_for_song:
self.next_page()
# ------------------------------------------------------
# Nalaganje in obdelava pesmi
# ------------------------------------------------------
def load_song(self):
if not self.song_number:
return
if self.song_number == "0":
self.clear_screen()
self.song_number = ""
return
# Ponastavitev izgleda barvnega območja
self.color_frame.config(bg=self.settings["bg_color"], width=self.color_width, height=self.screen_height)
self.color_frame.place(relx=0.5, rely=0.5, anchor="center")
self.label.config(bg=self.settings["bg_color"], fg=self.settings["fg_color"])
self.label.pack(expand=True)
# Posebni ukazi
if self.song_number == "9999":
subprocess.Popen(["shutdown", "/s", "/f", "/t", "0"])
return
elif self.song_number == "7777":
subprocess.Popen(["python", "nastavitve.py"])
self.exit_program()
return
elif self.song_number == "8888":
self.exit_program()
return
elif self.song_number == "6666":
subprocess.Popen(["python", "add_song.py"])
self.exit_program()
return
try:
song_id = int(self.song_number)
self.cursor.execute("SELECT lyrics FROM songs WHERE id=?", (song_id,))
result = self.cursor.fetchone()
if result:
lyrics = result[0]
max_height = self.screen_height - (self.line_height * 1.5)
avg_char_width = int(self.settings["font_size"]) * 0.57
wraplength = self.color_width
# -------------------------------------------
# 1. Razbijanje besedila na (pod-)vrstice
# -------------------------------------------
raw_lines = lyrics.strip().splitlines()
processed_lines = []
for raw in raw_lines:
if raw.strip() == "":
processed_lines.append("") # ohranimo prazno vrstico
else:
processed_lines.extend(self.split_long_line(raw))
# -------------------------------------------
# 2. Razdeljevanje vrstic na strani
# -------------------------------------------
pages = []
current_page_lines = []
current_height = 0
blank_line_count = 0
for line in processed_lines:
is_blank = (line.strip() == "")
if is_blank:
blank_line_count += 1
else:
blank_line_count = 0
approx_line_length = len(line)
approx_line_count = 1 if is_blank else math.ceil((approx_line_length * avg_char_width) / wraplength)
needed_height = self.line_height * approx_line_count
# Prazna vrstica več kot 1krat pomeni nova kitica ⇒ nova stran
if blank_line_count >= 2:
if current_page_lines:
pages.append("\n".join(current_page_lines).strip())
current_page_lines = []
current_height = 0
blank_line_count = 0
continue
# Če čez spodnji rob strani …
if current_height + needed_height > max_height + self.line_height // 2:
# poskusimo prelomiti na prazni vrstici znotraj strani
found_split = False
for i in reversed(range(len(current_page_lines))):
if current_page_lines[i].strip() == "":
pages.append("\n".join(current_page_lines[:i]).strip())
current_page_lines = current_page_lines[i + 1:]
current_height = sum(
self.line_height * (
1 if l.strip() == "" else math.ceil(len(l) * avg_char_width / wraplength)
)
for l in current_page_lines
)
found_split = True
break
if not found_split:
pages.append("\n".join(current_page_lines).strip())
current_page_lines = []
current_height = 0
current_page_lines.append(line)
current_height += needed_height
if current_page_lines:
pages.append("\n".join(current_page_lines).strip())
self.pages = pages
self.current_page_index = 0
self.waiting_for_song = False
self.song_number_last = self.song_number
self.show_page()
else:
self.label.config(text="")
self.pages = []
self.current_page_index = 0
self.waiting_for_song = True
self.song_number_last = self.song_number
self.song_info_label.config(text=self.song_number_last)
self.song_info_label.lift()
except Exception as e:
self.label.config(text=f"Napaka: {e}")
finally:
self.song_number = ""
# ------------------------------------------------------
# Prikaz trenutne strani
# ------------------------------------------------------
def show_page(self):
if self.pages:
text = self.pages[self.current_page_index]
if self.all_caps_mode:
text = text.upper()
self.label.config(text=text)
if self.settings.get("show_song_info", False):
current_page = self.current_page_index + 1
total_pages = len(self.pages)
self.song_info_label.config(text=f"{self.song_number_last} {current_page}/{total_pages}")
self.song_info_label.lift()
else:
self.song_info_label.config(text="")
# ------------------------------------------------------
# Navigacija po straneh
# ------------------------------------------------------
def next_page(self, event=None):
if self.pages and self.current_page_index + 1 < len(self.pages):
self.current_page_index += 1
self.show_page()
def prev_page(self, event=None):
if self.pages and self.current_page_index > 0:
self.current_page_index -= 1
self.show_page()
# ------------------------------------------------------
# Očisti zaslon
# ------------------------------------------------------
def clear_screen(self, event=None):
self.label.config(text="")
self.label.pack_forget()
self.color_frame.config(bg="black", width=self.color_width, height=self.screen_height)
self.color_frame.place(relx=0.5, rely=0.5, anchor="center")
self.song_info_label.config(text="")
self.waiting_for_song = True
# ------------------------------------------------------
# Iskanje pesmi
# ------------------------------------------------------
def search_song(self, event=None):
self.clear_screen()
if self.search_label:
self.search_label.destroy()
if self.search_entry:
self.search_entry.destroy()
self.search_label = tk.Label(
self.color_frame,
text="Vpiši del naslova:",
bg=self.settings["bg_color"],
fg=self.settings["fg_color"],
font=(self.settings["font_name"], int(self.settings["font_size"])),
wraplength=self.color_width,
justify="center"
)
self.search_label.pack(pady=(40, 10))
self.search_entry = tk.Entry(
self.color_frame,
font=(self.settings["font_name"], int(self.settings["font_size"])),
justify="center",
bg=self.settings["bg_color"],
fg=self.settings["fg_color"],
insertbackground=self.settings["fg_color"],
borderwidth=0,
highlightthickness=0
)
self.search_entry.pack()
self.search_entry.focus()
self.search_entry.bind("<Return>", self.perform_search)
def perform_search(self, event=None):
query = self.search_entry.get().strip().lower()
self.search_label.destroy()
self.search_entry.destroy()
if query:
self.cursor.execute("SELECT id, title FROM songs")
all_results = self.cursor.fetchall()
matched = []
for song_id, title in all_results:
if query in title.lower():
matched.append((song_id, title))
if matched:
found = "\n".join(f"{id}: {title}" for id, title in matched)
self.label.pack(expand=True)
self.label.config(text=found)
else:
self.label.pack(expand=True)
self.label.config(text="Ni zadetkov.")
# ------------------------------------------------------
# Izhod
# ------------------------------------------------------
def exit_program(self, event=None):
self.conn.close()
self.root.destroy()
# ----------------------------------------------------------
# Zagon aplikacije
# ----------------------------------------------------------
if __name__ == "__main__":
root = tk.Tk()
app = SongProjector(root)
root.mainloop()

10
settings.json Normal file
View File

@@ -0,0 +1,10 @@
{
"font_name": "Times New Roman",
"bg_color": "#000000",
"fg_color": "#FFFFFF",
"font_size": 38,
"screen_width_percent": 60,
"font_bold": true,
"show_song_info": true,
"split_by_stanza": true
}