figures_name_tool.pyw 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import tkinter as tk
  2. from tkinter import ttk, filedialog, Canvas, Frame, Toplevel, messagebox
  3. from PIL import Image, ImageTk
  4. from collections import Counter
  5. import requests
  6. from io import BytesIO
  7. import re
  8. import os
  9. import shutil
  10. LANGUAGE_MAPPING = {
  11. "de-de": "German",
  12. "en-gb": "English",
  13. "en-us": "English",
  14. "fr-fr": "French"
  15. }
  16. # Max Image Size
  17. MAX_WIDTH = 100
  18. MAX_HEIGHT = 100
  19. COLS = 3 # Count of columns
  20. file_path = ""
  21. # GUI erstellen
  22. root = tk.Tk()
  23. root.title("Tonie Name Tool")
  24. root.geometry("400x500")
  25. def get_language_name(language_code):
  26. """returns the folder name for the selected language"""
  27. return LANGUAGE_MAPPING.get(language_code, language_code) # default to the lang_code if no mapping exists
  28. def generate_valid_filename(name, max_length=255):
  29. # remove invalid characters
  30. name = re.sub(r'[<>:"/\\|?*\x00-\x1F]', "", name)
  31. name = name.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss") # replace umlauts
  32. # remove leading/trailing whitespaces
  33. name = name.strip()
  34. # truncate to max_length
  35. if len(name) > max_length:
  36. name = name[:max_length]
  37. return name
  38. def load_new_file():
  39. """ fuction to load a new file """
  40. global file_path
  41. file_path = filedialog.askopenfilename(filetypes=[("NFC-File", "*.nfc")])
  42. if not file_path:
  43. return # No file selected
  44. # Update the dropdowns
  45. choose_language_show_series()
  46. def choose_language_show_series(*args):
  47. """ function to show the series of the selected language """
  48. lang = selected_language.get()
  49. language_series = [entry for entry in data if entry.get("language") == lang and (entry["hash"] or (entry["episodes"] is not None and entry["episodes"].find("Set") >= 0))]
  50. unique_series = sorted(set(entry.get("series") for entry in language_series), reverse=False)
  51. dropdown_series["values"] = unique_series
  52. dropdown_series["state"] = "readonly" if unique_series else "disabled"
  53. selected_series.set("") # reset the series
  54. def show_episode_details(entry):
  55. """ shows the choosen episode in a detail window """
  56. details_window = Toplevel(root)
  57. details_window.title("Episode details")
  58. # title
  59. tk.Label(details_window, text=entry["episodes"], font=("Arial", 14, "bold")).pack(pady=10)
  60. # load image from url
  61. try:
  62. response = requests.get(entry["pic"])
  63. img_data = Image.open(BytesIO(response.content))
  64. # scale image to fit into the window
  65. img_data.thumbnail((400, 400))
  66. img = ImageTk.PhotoImage(img_data)
  67. img_label = tk.Label(details_window, image=img)
  68. img_label.image = img
  69. img_label.pack(pady=10)
  70. except Exception as e:
  71. print(f"Error while loading the image {e}")
  72. def on_ok():
  73. """ function to copy the file to the selected folder """
  74. global file_path
  75. lang = selected_language.get()
  76. series = selected_series.get()
  77. episode = entry["episodes"]
  78. selected_data = [entry for entry in data if entry["language"] == lang and entry["series"] == series and entry["episodes"] == episode]
  79. #audio_id = [entry["audio_id"] for entry in selected_data]
  80. language_folder = generate_valid_filename(get_language_name(lang))
  81. series_folder = generate_valid_filename(series)
  82. file_name = generate_valid_filename(episode)
  83. details_window.destroy() # close the window
  84. destination_path = os.path.join("..", language_folder, series_folder,)
  85. os.makedirs(destination_path, exist_ok=True) # create the folder if it does not exist
  86. try:
  87. shutil.copy(file_path, os.path.join(destination_path, file_name + ".nfc"))
  88. except shutil.SameFileError:
  89. # file already exists
  90. messagebox.showinfo("Error", f"The file {file_name} already exists in {language_folder}/{series_folder}")
  91. return
  92. messagebox.showinfo("Selection confirmed", f"The Tonie is stored under {language_folder}/{series_folder}/{file_name}")
  93. # Add OK-Button
  94. tk.Button(details_window, text="OK", command=on_ok).pack(pady=10)
  95. # Add Close-Button
  96. tk.Button(details_window, text="Close", command=details_window.destroy).pack(pady=5)
  97. def choose_series_show_episodes(*args):
  98. """ function to show the episodes of the selected series """
  99. lang = selected_language.get()
  100. series = selected_series.get()
  101. # reset the scroll frame
  102. for widget in scroll_frame.winfo_children():
  103. widget.destroy()
  104. # filter the data
  105. episodes = [entry for entry in data if entry["language"] == lang and entry["series"] == series and (entry["hash"] or (entry["episodes"] is not None and entry["episodes"].find("Set") >= 0))]
  106. filtered_episodes = []
  107. for entry in episodes:
  108. if entry["episodes"].find("Set") >= 0 and "tracks" in entry:
  109. for track in entry["tracks"]:
  110. new_entry = entry.copy()
  111. new_entry["episodes"] = track
  112. filtered_episodes.append(new_entry)
  113. else:
  114. filtered_episodes.append(entry)
  115. # create the grid
  116. row , col = 0, 0
  117. for entry in filtered_episodes:
  118. # load the image from the url
  119. response = requests.get(entry["pic"])
  120. img_data = Image.open(BytesIO(response.content))
  121. # scale image
  122. img_data.thumbnail((MAX_WIDTH, MAX_HEIGHT)) # Automatically scale to fit the frame
  123. img = ImageTk.PhotoImage(img_data)
  124. # Show Image
  125. # Image-Button with function
  126. img_button = tk.Button(scroll_frame, image=img, command=lambda e=entry: show_episode_details(e))
  127. img_button.image = img
  128. img_button.grid(row=row, column=col, padx=10, pady=10)
  129. # episode-title
  130. episode_label = tk.Label(scroll_frame, text=entry["episodes"], wraplength=100, justify="center")
  131. episode_label.grid(row=row + 1, column=col, padx=10, pady=5)
  132. col += 1
  133. if col >= COLS: # Max 3 Columns
  134. col = 0
  135. row += 2
  136. # Scrollbar update
  137. scroll_frame.update_idletasks()
  138. canvas.config(scrollregion=canvas.bbox("all"))
  139. # get the full data from the json file
  140. url="https://raw.githubusercontent.com/toniebox-reverse-engineering/tonies-json/release/tonies.json"
  141. try:
  142. response = requests.get(url, timeout=10)
  143. response.raise_for_status()
  144. data = response.json()
  145. except requests.RequestException as e:
  146. print(f"Error while loading the data: {e}")
  147. data = []
  148. # filter the data by all languages containing figures
  149. languages = [entry.get("language") for entry in data if entry.get("hash") and entry.get("episodes") is not None]
  150. filtered_languages = [lang for lang in languages if isinstance(lang, str)]
  151. language_counts = Counter(filtered_languages)
  152. sorted_languages = sorted(language_counts.keys(), key=lambda x: language_counts[x], reverse=True)
  153. # Buttons and Dropdowns
  154. tk.Button(root, text="Choose new file", command=load_new_file).pack(pady=10)
  155. # Dropdown language
  156. tk.Label(root, text="Choose a language:").pack(pady=5)
  157. selected_language = tk.StringVar()
  158. dropdown_language = ttk.Combobox(root, textvariable=selected_language, values=sorted_languages, state="readonly", width=50)
  159. dropdown_language.pack(pady=5)
  160. # Dropdown series
  161. tk.Label(root, text="Choose a series:").pack(pady=5)
  162. selected_series = tk.StringVar()
  163. dropdown_series = ttk.Combobox(root, textvariable=selected_series, state="disabled", width=50)
  164. dropdown_series.pack(pady=5)
  165. # Frame for Scrollbar
  166. canvas = Canvas(root)
  167. scroll_frame = Frame(canvas)
  168. scrollbar = ttk.Scrollbar(root, orient="vertical", command=canvas.yview)
  169. canvas.configure(yscrollcommand=scrollbar.set)
  170. scrollbar.pack(side="right", fill="y")
  171. canvas.pack(side="left", fill="both", expand=True)
  172. canvas.create_window((0, 0), window=scroll_frame, anchor="nw")
  173. # Event-listener for the language change
  174. selected_language.trace_add("write", choose_language_show_series)
  175. # Event-listener for the series change
  176. selected_series.trace_add("write", choose_series_show_episodes)
  177. root.mainloop()