|
|
@@ -0,0 +1,198 @@
|
|
|
+import tkinter as tk
|
|
|
+from tkinter import ttk, filedialog, Canvas, Frame, Toplevel, messagebox
|
|
|
+from PIL import Image, ImageTk
|
|
|
+from collections import Counter
|
|
|
+import requests
|
|
|
+from io import BytesIO
|
|
|
+import re
|
|
|
+import os
|
|
|
+import shutil
|
|
|
+
|
|
|
+LANGUAGE_MAPPING = {
|
|
|
+ "de-de": "German",
|
|
|
+ "en-gb": "English",
|
|
|
+ "en-us": "English",
|
|
|
+ "fr-fr": "French"
|
|
|
+}
|
|
|
+
|
|
|
+# Max Image Size
|
|
|
+MAX_WIDTH = 100
|
|
|
+MAX_HEIGHT = 100
|
|
|
+COLS = 3 # Count of columns
|
|
|
+file_path = ""
|
|
|
+
|
|
|
+# GUI erstellen
|
|
|
+root = tk.Tk()
|
|
|
+root.title("Tonie Name Tool")
|
|
|
+root.geometry("400x500")
|
|
|
+
|
|
|
+def get_language_name(language_code):
|
|
|
+ """returns the folder name for the selected language"""
|
|
|
+ return LANGUAGE_MAPPING.get(language_code, language_code) # default to the lang_code if no mapping exists
|
|
|
+
|
|
|
+def generate_valid_filename(name, max_length=255):
|
|
|
+ # remove invalid characters
|
|
|
+ name = re.sub(r'[<>:"/\\|?*\x00-\x1F]', "", name)
|
|
|
+ name = name.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss") # replace umlauts
|
|
|
+ # remove leading/trailing whitespaces
|
|
|
+ name = name.strip()
|
|
|
+
|
|
|
+ # truncate to max_length
|
|
|
+ if len(name) > max_length:
|
|
|
+ name = name[:max_length]
|
|
|
+
|
|
|
+ return name
|
|
|
+
|
|
|
+def load_new_file():
|
|
|
+ """ fuction to load a new file """
|
|
|
+ global file_path
|
|
|
+ file_path = filedialog.askopenfilename(filetypes=[("NFC-File", "*.nfc")])
|
|
|
+ if not file_path:
|
|
|
+ return # No file selected
|
|
|
+
|
|
|
+ # Update the dropdowns
|
|
|
+ choose_language_show_series()
|
|
|
+
|
|
|
+def choose_language_show_series(*args):
|
|
|
+ """ function to show the series of the selected language """
|
|
|
+ lang = selected_language.get()
|
|
|
+ language_series = [entry for entry in data if entry.get("language") == lang and entry.get("hash")]
|
|
|
+ unique_series = sorted(set(entry.get("series") for entry in language_series), reverse=False)
|
|
|
+ dropdown_series["values"] = unique_series
|
|
|
+ dropdown_series["state"] = "readonly" if unique_series else "disabled"
|
|
|
+ selected_series.set("") # reset the series
|
|
|
+
|
|
|
+def show_episode_details(entry):
|
|
|
+ """ shows the choosen episode in a detail window """
|
|
|
+ details_window = Toplevel(root)
|
|
|
+ details_window.title("Episode details")
|
|
|
+
|
|
|
+ # title
|
|
|
+ tk.Label(details_window, text=entry["episodes"], font=("Arial", 14, "bold")).pack(pady=10)
|
|
|
+
|
|
|
+ # load image from url
|
|
|
+ try:
|
|
|
+ response = requests.get(entry["pic"])
|
|
|
+ img_data = Image.open(BytesIO(response.content))
|
|
|
+
|
|
|
+ # scale image to fit into the window
|
|
|
+ img_data.thumbnail((200, 200))
|
|
|
+ img = ImageTk.PhotoImage(img_data)
|
|
|
+
|
|
|
+ img_label = tk.Label(details_window, image=img)
|
|
|
+ img_label.image = img
|
|
|
+ img_label.pack(pady=10)
|
|
|
+ except Exception as e:
|
|
|
+ print(f"Error while loading the image {e}")
|
|
|
+
|
|
|
+ def on_ok():
|
|
|
+ """ function to copy the file to the selected folder """
|
|
|
+ global file_path
|
|
|
+ lang = selected_language.get()
|
|
|
+ series = selected_series.get()
|
|
|
+ episode = entry["episodes"]
|
|
|
+ selected_data = [entry for entry in data if entry["language"] == lang and entry["series"] == series and entry["episodes"] == episode]
|
|
|
+ #audio_id = [entry["audio_id"] for entry in selected_data]
|
|
|
+ language_folder = generate_valid_filename(get_language_name(lang))
|
|
|
+ series_folder = generate_valid_filename(series)
|
|
|
+ file_name = generate_valid_filename(episode)
|
|
|
+ details_window.destroy() # close the window
|
|
|
+ destination_path = os.path.join(language_folder, series_folder,)
|
|
|
+ os.makedirs(destination_path, exist_ok=True) # create the folder if it does not exist
|
|
|
+ shutil.copy(file_path, os.path.join(destination_path, file_name + ".nfc"))
|
|
|
+ messagebox.showinfo("Selection confirmed", f"The Tonie is stored under {language_folder}/{series_folder}/{file_name}")
|
|
|
+
|
|
|
+ # Add OK-Button
|
|
|
+ tk.Button(details_window, text="OK", command=on_ok).pack(pady=10)
|
|
|
+ # Add Close-Button
|
|
|
+ tk.Button(details_window, text="Close", command=details_window.destroy).pack(pady=5)
|
|
|
+
|
|
|
+def choose_series_show_episodes(*args):
|
|
|
+ """ function to show the episodes of the selected series """
|
|
|
+ lang = selected_language.get()
|
|
|
+ series = selected_series.get()
|
|
|
+
|
|
|
+ # reset the scroll frame
|
|
|
+ for widget in scroll_frame.winfo_children():
|
|
|
+ widget.destroy()
|
|
|
+
|
|
|
+ # filter the data
|
|
|
+ filtered_episodes = [entry for entry in data if entry["language"] == lang and entry["series"] == series and entry["hash"]]
|
|
|
+
|
|
|
+ # create the grid
|
|
|
+ row , col = 0, 0
|
|
|
+ for entry in filtered_episodes:
|
|
|
+ # load the image from the url
|
|
|
+ response = requests.get(entry["pic"])
|
|
|
+ img_data = Image.open(BytesIO(response.content))
|
|
|
+
|
|
|
+ # scale image
|
|
|
+ img_data.thumbnail((MAX_WIDTH, MAX_HEIGHT)) # Automatically scale to fit the frame
|
|
|
+ img = ImageTk.PhotoImage(img_data)
|
|
|
+
|
|
|
+ # Show Image
|
|
|
+ # Image-Button with function
|
|
|
+ img_button = tk.Button(scroll_frame, image=img, command=lambda e=entry: show_episode_details(e))
|
|
|
+ img_button.image = img
|
|
|
+ img_button.grid(row=row, column=col, padx=10, pady=10)
|
|
|
+
|
|
|
+ # episode-title
|
|
|
+ episode_label = tk.Label(scroll_frame, text=entry["episodes"], wraplength=100, justify="center")
|
|
|
+ episode_label.grid(row=row + 1, column=col, padx=10, pady=5)
|
|
|
+
|
|
|
+ col += 1
|
|
|
+ if col >= COLS: # Max 3 Columns
|
|
|
+ col = 0
|
|
|
+ row += 2
|
|
|
+
|
|
|
+ # Scrollbar update
|
|
|
+ scroll_frame.update_idletasks()
|
|
|
+ canvas.config(scrollregion=canvas.bbox("all"))
|
|
|
+
|
|
|
+# get the full data from the json file
|
|
|
+url="https://raw.githubusercontent.com/toniebox-reverse-engineering/tonies-json/release/tonies.json"
|
|
|
+try:
|
|
|
+ response = requests.get(url, timeout=10)
|
|
|
+ response.raise_for_status()
|
|
|
+ data = response.json()
|
|
|
+except requests.RequestException as e:
|
|
|
+ print(f"Error while loading the data: {e}")
|
|
|
+ data = []
|
|
|
+
|
|
|
+# filter the data by all languages containing figures
|
|
|
+languages = [entry.get("language") for entry in data if entry.get("hash")]
|
|
|
+filtered_languages = [lang for lang in languages if isinstance(lang, str)]
|
|
|
+language_counts = Counter(filtered_languages)
|
|
|
+sorted_languages = sorted(language_counts.keys(), key=lambda x: language_counts[x], reverse=True)
|
|
|
+
|
|
|
+# Buttons and Dropdowns
|
|
|
+tk.Button(root, text="Choose new file", command=load_new_file).pack(pady=10)
|
|
|
+
|
|
|
+# Dropdown language
|
|
|
+tk.Label(root, text="Choose a language:").pack(pady=5)
|
|
|
+selected_language = tk.StringVar()
|
|
|
+dropdown_language = ttk.Combobox(root, textvariable=selected_language, values=sorted_languages, state="readonly", width=50)
|
|
|
+dropdown_language.pack(pady=5)
|
|
|
+
|
|
|
+# Dropdown series
|
|
|
+tk.Label(root, text="Choose a series:").pack(pady=5)
|
|
|
+selected_series = tk.StringVar()
|
|
|
+dropdown_series = ttk.Combobox(root, textvariable=selected_series, state="disabled", width=50)
|
|
|
+dropdown_series.pack(pady=5)
|
|
|
+
|
|
|
+# Frame for Scrollbar
|
|
|
+canvas = Canvas(root)
|
|
|
+scroll_frame = Frame(canvas)
|
|
|
+scrollbar = ttk.Scrollbar(root, orient="vertical", command=canvas.yview)
|
|
|
+canvas.configure(yscrollcommand=scrollbar.set)
|
|
|
+
|
|
|
+scrollbar.pack(side="right", fill="y")
|
|
|
+canvas.pack(side="left", fill="both", expand=True)
|
|
|
+canvas.create_window((0, 0), window=scroll_frame, anchor="nw")
|
|
|
+
|
|
|
+# Event-listener for the language change
|
|
|
+selected_language.trace_add("write", choose_language_show_series)
|
|
|
+# Event-listener for the series change
|
|
|
+selected_series.trace_add("write", choose_series_show_episodes)
|
|
|
+
|
|
|
+root.mainloop()
|