import tkinter as tk from tkinter import ttk, messagebox import json import os import subprocess import shutil import gzip import concurrent.futures import functools from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import padding selected_songs = set() selected_song_ids = [] # Function to load configuration from file def load_config(): config_file = "config.json" default_config = { "max_concurrent": 5, # Default value if not specified in config file "lang": "en", "custom_songs": False, "custom_song_path": "data_custom/" } try: with open(config_file, "r") as f: config = json.load(f) # Override default values with values from config file default_config.update(config) except FileNotFoundError: print(f"Config file '{config_file}' not found. Using default configuration.") return default_config data_dir = "data/" musicinfo_path = os.path.join(data_dir, "datatable", "musicinfo.json") wordlist_path = os.path.join(data_dir, "datatable", "wordlist.json") previewpos_path = os.path.join(data_dir, "datatable", "previewpos.json") # Load configuration config = load_config() custom_songs = config["custom_songs"] lang = config["lang"] if custom_songs == True: print("Custom Song Loading Enabled") custom_data_dir = config.get('custom_song_path') custom_musicinfo_path = os.path.join(custom_data_dir, "datatable", "musicinfo.json") custom_wordlist_path = os.path.join(custom_data_dir, "datatable", "wordlist.json") custom_previewpos_path = os.path.join(custom_data_dir, "datatable", "previewpos.json") item_selection_state = {} with open(musicinfo_path, "r", encoding="utf-8") as musicinfo_file: music_info = json.load(musicinfo_file) with open(wordlist_path, "r", encoding="utf-8") as wordlist_file: word_list = json.load(wordlist_file) if custom_songs == True: with open(custom_musicinfo_path, "r", encoding="utf-8") as custom_musicinfo_file: custom_music_info = json.load(custom_musicinfo_file) with open(custom_wordlist_path, "r", encoding="utf-8") as custom_wordlist_file: custom_word_list = json.load(custom_wordlist_file) if lang == "jp": genre_map = { 0: ("ポップス", "light blue"), 1: ("アニメ", "orange"), 2: ("ボーカロイド", "turquoise"), 3: ("バラエティ", "green"), 4: ("Unused", "gray"), 5: ("クラシック", "dark red"), 6: ("ゲームミュージック", "purple"), 7: ("ナムコオリジナル", "dark orange"), } else: genre_map = { 0: ("POP", "light blue"), 1: ("Anime", "orange"), 2: ("Vocaloid", "turquoise"), 3: ("Variety", "green"), 4: ("Unused", "gray"), 5: ("Classic", "dark red"), 6: ("Game Music", "purple"), 7: ("Namco Original", "dark orange"), } if lang == "jp": song_titles = {item["key"]: item["japaneseText"] for item in word_list["items"]} song_subtitles = {item["key"]: item["japaneseText"] for item in word_list["items"]} else: song_titles = {item["key"]: item["englishUsText"] for item in word_list["items"]} song_subtitles = {item["key"]: item["englishUsText"] for item in word_list["items"]} if custom_songs == True: if lang == "jp": custom_song_titles = {item["key"]: item["japaneseText"] for item in custom_word_list["items"]} custom_song_subtitles = {item["key"]: item["japaneseText"] for item in custom_word_list["items"]} else: custom_song_titles = {item["key"]: item["englishUsText"] for item in custom_word_list["items"]} custom_song_subtitles = {item["key"]: item["englishUsText"] for item in custom_word_list["items"]} window = tk.Tk() window.title("Taiko no Tatsujin Song Conversion GUI Tool") # Set the initial size of the window window.geometry("1000x600") # Width x Height # Create a frame to contain the Treeview and scrollbar main_frame = ttk.Frame(window) main_frame.pack(fill="both", expand=True, padx=10, pady=10) # Create Treeview and Scrollbar tree = ttk.Treeview(main_frame, columns=("Select", "Unique ID", "ID", "Song Name", "Song Subtitle", "Genre", "Difficulty"), show="headings", selectmode="extended") if lang == "jp": tree.heading("Song Name", text="曲") tree.heading("Song Subtitle", text="曲名") tree.heading("Genre", text="ジャンル順") tree.heading("Difficulty", text="むずかしさ") tree.heading("Select", text="移動") else: tree.heading("Song Name", text="Song Name") tree.heading("Song Subtitle", text="Song Subtitle") tree.heading("Genre", text="Genre") tree.heading("Difficulty", text="Difficulty") tree.heading("Select", text="Select") tree.heading("Unique ID", text="") tree.heading("ID", text="ID") tree.column("Select", width=50, anchor=tk.CENTER) tree.column("Unique ID", width=0, anchor=tk.W) tree.column("ID", width=60, anchor=tk.W) tree.column("Song Name", anchor=tk.W) tree.column("Song Subtitle", anchor=tk.W) tree.column("Genre", width=100, anchor=tk.W) tree.column("Difficulty", width=120, anchor=tk.W) vsb = ttk.Scrollbar(main_frame, orient="vertical", command=tree.yview) tree.configure(yscrollcommand=vsb.set) # Pack Treeview and Scrollbar into the main_frame tree.pack(side="left", fill="both", expand=True) vsb.pack(side="right", fill="y") # Counter for selected items selection_count = tk.IntVar() selection_count.set(0) # Initial selection count def on_search_keyrelease(event): print("Key released:", event.keysym) #filter_treeview() # Search Entry search_label = tk.Label(window, text="Filter Songs:", anchor="w") search_label.pack(side="top", padx=20, pady=0, anchor="w") search_var = tk.StringVar() search_entry = ttk.Entry(window, textvariable=search_var) search_entry.pack(side="bottom", fill="x", padx=10, pady=10) def toggle_checkbox(event): selected_items = tree.selection() for item_id in selected_items: values = list(tree.item(item_id, "values")) song_id = values[2] # Assume the song ID is in the third column if values[0] == "☐": values[0] = "☑" if song_id not in selected_song_ids: selected_song_ids.append(song_id) selection_count.set(selection_count.get() + 1) # Increment selection count else: values[0] = "☐" if song_id in selected_song_ids: selected_song_ids.remove(song_id) selection_count.set(selection_count.get() - 1) # Decrement selection count tree.item(item_id, values=values) update_selection_count() # Call update_selection_count to update the count instantly return "break" def filter_treeview(): search_text = search_var.get().strip().lower() populate_tree(search_text) # Populate Treeview with filtered data def populate_tree(search_text=""): # Clear existing items in the Treeview tree.delete(*tree.get_children()) def add_song_to_tree(song, title_dict, subtitle_dict): song_id = f"{song['id']}" genre_no = song["genreNo"] genre_name, genre_color = genre_map.get(genre_no, ("Unknown Genre", "white")) english_title = title_dict.get(f"song_{song_id}", "-") english_subtitle = subtitle_dict.get(f"song_sub_{song_id}", "-") star_easy = song.get("starEasy", "N/A") star_normal = song.get("starNormal", "N/A") star_hard = song.get("starHard", "N/A") star_mania = song.get("starMania", "N/A") star_ura = song.get("starUra", 0) difficulty_info_parts = [ f"{star_easy}", f"{star_normal}", f"{star_hard}", f"{star_mania}", ] if star_ura > 0: difficulty_info_parts.append(f"{star_ura}") difficulty_info = " | ".join(difficulty_info_parts) # Check if the search text matches the song name if search_text in english_title.lower(): values = ["☐", "", song_id, english_title, english_subtitle, genre_name, difficulty_info] if song_id in selected_song_ids: values[0] = "☑" item_id = tree.insert("", "end", values=values) tree.tag_configure(genre_name, background=genre_color) # Re-select item if it was previously selected if song_id in selected_song_ids: tree.selection_add(item_id) for song in sorted(music_info["items"], key=lambda x: x["id"]): # Sort by ID add_song_to_tree(song, song_titles, song_subtitles) if custom_songs: for song in sorted(custom_music_info["items"], key=lambda x: x["id"]): # Sort by ID add_song_to_tree(song, custom_song_titles, custom_song_subtitles) search_entry.bind("", lambda event: filter_treeview()) def sort_tree(sort_option): # Clear existing items in the Treeview tree.delete(*tree.get_children()) def add_sorted_songs(sorted_songs, title_dict, subtitle_dict): for song in sorted_songs: song_id = f"{song['id']}" genre_no = song["genreNo"] genre_name, genre_color = genre_map.get(genre_no, ("Unknown Genre", "white")) english_title = title_dict.get(f"song_{song_id}", "-") english_subtitle = subtitle_dict.get(f"song_sub_{song_id}", "-") star_easy = song.get("starEasy", "N/A") star_normal = song.get("starNormal", "N/A") star_hard = song.get("starHard", "N/A") star_mania = song.get("starMania", "N/A") star_ura = song.get("starUra", 0) difficulty_info_parts = [ f"{star_easy}", f"{star_normal}", f"{star_hard}", f"{star_mania}", ] if star_ura > 0: difficulty_info_parts.append(f"{star_ura}") difficulty_info = " | ".join(difficulty_info_parts) values = ["☐", "", song_id, english_title, english_subtitle, genre_name, difficulty_info] if song_id in selected_song_ids: values[0] = "☑" item_id = tree.insert("", "end", values=values) tree.tag_configure(genre_name, background=genre_color) # Re-select item if it was previously selected if song_id in selected_song_ids: tree.selection_add(item_id) if sort_option == "ID": sorted_songs = sorted(music_info["items"], key=lambda x: x["id"]) add_sorted_songs(sorted_songs, song_titles, song_subtitles) if custom_songs: sorted_custom_songs = sorted(custom_music_info["items"], key=lambda x: x["id"]) add_sorted_songs(sorted_custom_songs, custom_song_titles, custom_song_subtitles) elif sort_option == "Song Name": sorted_songs = sorted(music_info["items"], key=lambda x: song_titles.get(f"song_{x['id']}", "-")) add_sorted_songs(sorted_songs, song_titles, song_subtitles) if custom_songs: sorted_custom_songs = sorted(custom_music_info["items"], key=lambda x: custom_song_titles.get(f"song_{x['id']}", "-")) add_sorted_songs(sorted_custom_songs, custom_song_titles, custom_song_subtitles) elif sort_option == "Genre": for genre_no in sorted(genre_map.keys()): sorted_songs = [song for song in music_info["items"] if song["genreNo"] == genre_no] add_sorted_songs(sorted_songs, song_titles, song_subtitles) if custom_songs: sorted_custom_songs = [song for song in custom_music_info["items"] if song["genreNo"] == genre_no] add_sorted_songs(sorted_custom_songs, custom_song_titles, custom_song_subtitles) def populate_song_entry(song): unique_id = "" song_id = f"{song['id']}" genre_no = song["genreNo"] genre_name, genre_color = genre_map.get(genre_no, ("Unknown Genre", "white")) english_title = song_titles.get(f"song_{song_id}", "-") english_subtitle = song_subtitles.get(f"song_sub_{song_id}", "-") if custom_songs == True: english_title = custom_song_titles.get(f"song_{song_id}", "-") english_subtitle = custom_song_subtitles.get(f"song_sub_{song_id}", "-") star_easy = song.get("starEasy", "N/A") star_normal = song.get("starNormal", "N/A") star_hard = song.get("starHard", "N/A") star_mania = song.get("starMania", "N/A") star_ura = song.get("starUra", 0) difficulty_info_parts = [ f"{star_easy}", f"{star_normal}", f"{star_hard}", f"{star_mania}", ] if star_ura > 0: difficulty_info_parts.append(f"{star_ura}") difficulty_info = " | ".join(difficulty_info_parts) item_id = tree.insert("", "end", values=("☐", unique_id, song_id, english_title, english_subtitle, genre_name, difficulty_info)) tree.tag_configure(genre_name, background=genre_color) # Populate the Treeview initially populate_tree() def update_selection_count(event=None): selected_items = tree.selection() count_selected = selection_count.get() # Retrieve the value of selection_count platform = game_platform_var.get() if platform == "PS4": max_entries = 400 elif platform == "NS1": max_entries = 600 elif platform == "PTB": max_entries = 200 else: max_entries = 0 if len(selected_items) > max_entries: messagebox.showerror("Selection Limit Exceeded", f"Maximum {max_entries} entries can be selected for {platform}.") else: # Update the selection count label text selection_count_label.config(text=f"{count_selected}/{max_entries}") # Bind the treeview selection event to update_selection_count function tree.bind("<>", update_selection_count) # Bind Treeview click event to toggle item selection #tree.bind("", lambda event: toggle_selection(tree.identify_row(event.y))) tree.bind("", toggle_checkbox) #tree.bind("", on_treeview_click) def preview_audio(song_id): preview_pos = get_preview_pos(song_id) if preview_pos is not None: song_filename = os.path.join(data_dir, "sound", f"song_{song_id}.mp3") subprocess.run(["ffplay", "-autoexit", "-ss", f"{preview_pos / 1000}", song_filename]) if custom_songs: custom_preview_pos = get_preview_pos(song_id) if custom_preview_pos is not None: custom_song_filename = os.path.join(custom_data_dir, "sound", f"song_{song_id}.mp3") subprocess.run(["ffplay", "-autoexit", "-ss", f"{custom_preview_pos / 1000}", custom_song_filename]) def get_preview_pos(song_id): # Load previewpos data from the default file with open(previewpos_path, "r", encoding="utf-8") as previewpos_file: previewpos_data = json.load(previewpos_file) for item in previewpos_data: if item["id"] == song_id: return item["previewPos"] # If use_custom is True, also try to load from the custom file if custom_songs: with open(custom_previewpos_path, "r", encoding="utf-8") as custom_previewpos_file: custom_previewpos_data = json.load(custom_previewpos_file) for item in custom_previewpos_data: if item["id"] == song_id: return item["previewPos"] return None def preview_selected(): selected_item = tree.selection() if selected_item: song_id = tree.item(selected_item[0])["values"][2] preview_audio(song_id) def merge_ptb(): command = [ "python", "script/ptb_wordlist.py", ] subprocess.run(command) def merge_ps4_int(): command = [ "python", "script/ps4_wordlist.py", ] subprocess.run(command) def merge_ps4_jp(): command = [ "python", "script/ps4_wordlist_jp.py", ] subprocess.run(command) def merge_ns1_int(): command = [ "python", "script/ns1_wordlist.py", ] subprocess.run(command) def merge_ns1_jp(): command = [ "python", "script/ns1_wordlist_jp.py", ] subprocess.run(command) def encrypt_file_ptb(input_file, output_file): # Generate a random initialization vector (IV) iv = os.urandom(16) # AES block size is 16 bytes # Pad the key if necessary (AES-128 requires a 16-byte key) key = bytes.fromhex("54704643596B474170554B6D487A597A") # Create an AES CBC cipher with the given key and IV cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) encryptor = cipher.encryptor() with open(input_file, 'rb') as f_in: with open(output_file, 'wb') as f_out: # Write the IV to the output file (needed for decryption) f_out.write(iv) # Encrypt the file chunk by chunk while True: chunk = f_in.read(16) # Read 16 bytes at a time if len(chunk) == 0: break elif len(chunk) % 16 != 0: # Add padding to the last block if needed padder = padding.PKCS7(128).padder() padded_data = padder.update(chunk) + padder.finalize() chunk = padded_data encrypted_chunk = encryptor.update(chunk) f_out.write(encrypted_chunk) # Finalize the encryption (encryptor might have remaining data) final_chunk = encryptor.finalize() f_out.write(final_chunk) def encrypt_file_ns1(input_file, output_file): # Generate a random initialization vector (IV) iv = os.urandom(16) # AES block size is 16 bytes # Pad the key if necessary (AES-128 requires a 16-byte key) key = bytes.fromhex("566342346438526962324A366334394B") # Create an AES CBC cipher with the given key and IV cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) encryptor = cipher.encryptor() with open(input_file, 'rb') as f_in: with open(output_file, 'wb') as f_out: # Write the IV to the output file (needed for decryption) f_out.write(iv) # Encrypt the file chunk by chunk while True: chunk = f_in.read(16) # Read 16 bytes at a time if len(chunk) == 0: break elif len(chunk) % 16 != 0: # Add padding to the last block if needed padder = padding.PKCS7(128).padder() padded_data = padder.update(chunk) + padder.finalize() chunk = padded_data encrypted_chunk = encryptor.update(chunk) f_out.write(encrypted_chunk) # Finalize the encryption (encryptor might have remaining data) final_chunk = encryptor.finalize() f_out.write(final_chunk) def gzip_compress_file(input_file_path): # Extract the base filename without extension file_name, file_ext = os.path.splitext(input_file_path) # Output file path with .gz extension appended output_file_path = f'{file_name}.gz' with open(input_file_path, 'rb') as f_in: with gzip.open(output_file_path, 'wb') as f_out: f_out.writelines(f_in) return output_file_path def gzip_compress_file_ps4(input_file_path): # Extract the base filename without extension file_name, file_ext = os.path.splitext(input_file_path) # Output file path with .gz extension appended output_file_path = f'{file_name}.bin' with open(input_file_path, 'rb') as f_in: with gzip.open(output_file_path, 'wb') as f_out: f_out.writelines(f_in) return output_file_path def copy_folder(source_folder, destination_folder): """ Copy the entire contents of source_folder to destination_folder. Args: source_folder (str): Path to the source folder to copy. destination_folder (str): Path to the destination folder. Returns: bool: True if copy operation is successful, False otherwise. """ try: # Check if destination folder already exists if os.path.exists(destination_folder): print(f"Destination folder '{destination_folder}' already exists. Skipping copy.") return False # Copy the entire folder from source to destination shutil.copytree(source_folder, destination_folder) print(f"Folder '{source_folder}' successfully copied to '{destination_folder}'.") return True except shutil.Error as e: print(f"Error: {e}") return False except OSError as e: print(f"Error: {e}") return False def export_data(): selected_items = [] for item_id in tree.get_children(): if tree.set(item_id, "Select") == "☑": selected_items.append(item_id) game_platform = game_platform_var.get() game_region = game_region_var.get() max_concurrent = config["max_concurrent"] processed_ids = set() # Track processed song IDs if game_platform == "PS4": output_dir = "out/Data/ORBIS/datatable" fumen_output_dir = "out/Data/ORBIS/fumen" fumen_hitwide_output_dir = "out/Data/ORBIS/fumen_hitwide" audio_output_dir = "out/Data/ORBIS/sound" musicinfo_filename = "musicinfo.json" max_entries = 400 # Maximum allowed entries for PS4 platform_tag = "ps4" elif game_platform == "NS1": output_dir = "out/Data/NX/datatable" fumen_output_dir = "out/Data/NX/fumen/enso" fumen_hitwide_output_dir = "out/Data/NX/fumen_hitwide/enso" fumen_hitnarrow_output_dir = "out/Data/NX/fumen_hitnarrow/enso" audio_output_dir = "out/Data/NX/sound" musicinfo_filename = "musicinfo.json" max_entries = 600 # Maximum allowed entries for NS1 platform_tag = "ns1" elif game_platform == "PTB": output_dir = "out/Data/Raw/ReadAssets" fumen_output_dir = "out/Data/Raw/fumen" audio_output_dir = "out/Data/Raw/sound/sound" musicinfo_filename = "musicinfo.json" songinfo_filename = "songinfo.json" max_entries = 200 # Maximum allowed entries for PTB platform_tag = "PTB" os.makedirs(output_dir, exist_ok=True) os.makedirs(fumen_output_dir, exist_ok=True) os.makedirs(audio_output_dir, exist_ok=True) selected_music_info = [] selected_song_info = [] selected_wordlist = [] current_unique_id = 0 try: if len(selected_items) > max_entries: messagebox.showerror("Selection Limit Exceeded", f"Maximum {max_entries} entries can be selected for {game_platform}.") return # Load preview position data with open(previewpos_path, "r", encoding="utf-8") as previewpos_file: previewpos_data = json.load(previewpos_file) if custom_songs: with open(custom_previewpos_path, "r", encoding="utf-8") as custom_previewpos_file: custom_previewpos_data = json.load(custom_previewpos_file) # Copy fumen folders for selected songs to output directory for item_id in selected_items: song_id = tree.item(item_id)["values"][2] fumen_folder_path = os.path.join(data_dir, "fumen", str(song_id)) if os.path.exists(fumen_folder_path): shutil.copytree(fumen_folder_path, os.path.join(fumen_output_dir, f"{song_id}")) song_info = next((item for item in music_info["items"] if item["id"] == song_id), None) if custom_songs: for item_id in selected_items: song_id = tree.item(item_id)["values"][2] custom_fumen_folder_path = os.path.join(custom_data_dir, "fumen", str(song_id)) if os.path.exists(custom_fumen_folder_path): shutil.copytree(custom_fumen_folder_path, os.path.join(fumen_output_dir, f"{song_id}")) song_info = next((item for item in custom_music_info["items"] if item["id"] == song_id), None) for item_id in selected_items: song_id = tree.item(item_id)["values"][2] if custom_songs: combined_items = custom_music_info["items"] + music_info["items"] else: combined_items = music_info["items"] song_info = next((item for item in combined_items if item["id"] == song_id), None) if song_info: # Calculate song_order based on genreNo and current_unique_id song_order = (int(song_info["genreNo"]) * 1000) + current_unique_id if game_platform == "NS1": ns1_song_info = { "id": song_info["id"], "uniqueId": current_unique_id, "songFileName": song_info["songFileName"], "order": song_order, "genreNo": song_info["genreNo"], "secretFlag":False, "dlc":False, "debug":False, "recording":True, "branchEasy": song_info["branchEasy"], "branchNormal": song_info["branchNormal"], "branchHard": song_info["branchHard"], "branchMania": song_info["branchMania"], "branchUra": song_info["branchUra"], "starEasy": song_info["starEasy"], "starNormal": song_info["starNormal"], "starHard": song_info["starHard"], "starMania": song_info["starMania"], "starUra": song_info["starUra"], "shinutiEasy": song_info["shinutiEasy"], "shinutiNormal": song_info["shinutiNormal"], "shinutiHard": song_info["shinutiHard"], "shinutiMania": song_info["shinutiMania"], "shinutiUra": song_info["shinutiUra"], "shinutiEasyDuet": song_info["shinutiEasyDuet"], "shinutiNormalDuet": song_info["shinutiNormalDuet"], "shinutiHardDuet": song_info["shinutiHardDuet"], "shinutiManiaDuet": song_info["shinutiManiaDuet"], "shinutiUraDuet": song_info["shinutiUraDuet"], "scoreEasy": song_info["scoreEasy"], "scoreNormal": song_info["scoreNormal"], "scoreHard": song_info["scoreHard"], "scoreMania": song_info["scoreMania"], "scoreUra": song_info["scoreUra"], "alleviationEasy": False, "alleviationNormal": False, "alleviationHard": False, "alleviationMania": False, "alleviationUra": False, "song_info1": 25721, "song_info2": 39634, "song_info3": 60504, "song_info4": 79618, "song_info5": 98750, "song_info6": -1, "song_info7": -1, "song_info8": -1, "song_info9": -1, "song_info10": -1, "aocID": song_info["id"], "limitedID": -1, "extraID": -1, "tournamentRand": True, "bgDon0": "", "bgDancer0": "", "bgFever0": "", "chibi0": "", "rendaEffect0": "", "dancer0": "", "feverEffect0": "", "bgDon1": "", "bgDancer1": "", "bgFever1": "", "chibi1": "", "rendaEffect1": "", "dancer1": "", "feverEffect1": "", } selected_music_info.append(ns1_song_info) elif game_platform == "PS4": ps4_song_info = { "id": song_info["id"], "uniqueId": current_unique_id, "songFileName": song_info["songFileName"], "order": song_order, "genreNo": song_info["genreNo"], "secretFlag":False, "dlc":False, "entitlementKey":"", "secondKey":False, "entitlementKey2":"", "debug":False, "branchEasy": song_info["branchEasy"], "branchNormal": song_info["branchNormal"], "branchHard": song_info["branchHard"], "branchMania": song_info["branchMania"], "branchUra": song_info["branchUra"], "starEasy": song_info["starEasy"], "starNormal": song_info["starNormal"], "starHard": song_info["starHard"], "starMania": song_info["starMania"], "starUra": song_info["starUra"], "shinutiEasy": song_info["shinutiEasy"], "shinutiNormal": song_info["shinutiNormal"], "shinutiHard": song_info["shinutiHard"], "shinutiMania": song_info["shinutiMania"], "shinutiUra": song_info["shinutiUra"], "shinutiEasyDuet": song_info["shinutiEasyDuet"], "shinutiNormalDuet": song_info["shinutiNormalDuet"], "shinutiHardDuet": song_info["shinutiHardDuet"], "shinutiManiaDuet": song_info["shinutiManiaDuet"], "shinutiUraDuet": song_info["shinutiUraDuet"], "scoreEasy": song_info["scoreEasy"], "scoreNormal": song_info["scoreNormal"], "scoreHard": song_info["scoreHard"], "scoreMania": song_info["scoreMania"], "scoreUra": song_info["scoreUra"], "secret":False, "songFileNameForSelect": song_info["songFileName"], "bgSolo0":"", "bgDuet0":"", "chibi0":"", "rendaEffect0":"", "dancer0":"", "feverEffect0":"", "bgSolo1":"", "bgDuet1":"", "chibi1":"", "rendaEffect1":"", "dancer1":"", "feverEffect1":"" } selected_music_info.append(ps4_song_info) elif game_platform == "PTB": ptb_song_info = { "uniqueId": current_unique_id, "id": song_info["id"], "songFileName": song_info["songFileName"], "order": song_order, "genreNo": song_info["genreNo"], "isLock":False, "isNew":False, "debug":False, "temp":False, "temp2":False, "branchEasy": song_info["branchEasy"], "branchNormal": song_info["branchNormal"], "branchHard": song_info["branchHard"], "branchMania": song_info["branchMania"], "branchUra": song_info["branchUra"], "starEasy": song_info["starEasy"], "starNormal": song_info["starNormal"], "starHard": song_info["starHard"], "starMania": song_info["starMania"], "starUra": song_info["starUra"], "shinutiEasy": song_info["shinutiEasy"], "shinutiNormal": song_info["shinutiNormal"], "shinutiHard": song_info["shinutiHard"], "shinutiMania": song_info["shinutiMania"], "shinutiUra": song_info["shinutiUra"], "shinutiEasyDuet": song_info["shinutiEasyDuet"], "shinutiNormalDuet": song_info["shinutiNormalDuet"], "shinutiHardDuet": song_info["shinutiHardDuet"], "shinutiManiaDuet": song_info["shinutiManiaDuet"], "shinutiUraDuet": song_info["shinutiUraDuet"], "scoreEasy": song_info["scoreEasy"], "scoreNormal": song_info["scoreNormal"], "scoreHard": song_info["scoreHard"], "scoreMania": song_info["scoreMania"], "scoreUra": song_info["scoreUra"], } selected_music_info.append(ptb_song_info) # Find previewPos from previewpos.json based on song_id preview_pos = None for item in previewpos_data: if item["id"] == song_info["id"]: preview_pos = item["previewPos"] break ptb_extra_song_info = { "uniqueId": current_unique_id, "id": song_info["id"], "previewPos": preview_pos if preview_pos is not None else 0, # Use 0 if previewPos not found "fumenOffsetPos":0 } selected_song_info.append(ptb_extra_song_info) current_unique_id += 1 # Find the wordlist items corresponding to song variations word_keys = [f"song_{song_id}", f"song_sub_{song_id}", f"song_detail_{song_id}"] def find_word_info(key, word_lists): for word_list in word_lists: word_info = next((item for item in word_list["items"] if item["key"] == key), None) if word_info: return word_info return None word_lists = [word_list] if custom_songs: word_lists.append(custom_word_list) for key in word_keys: word_info = find_word_info(key, word_lists) if word_info: selected_wordlist.append(word_info) if game_platform == "PS4": # Find the corresponding preview position for the current song_id preview_pos = next((item["previewPos"] for item in previewpos_data if item["id"] == song_id), None) if custom_songs: custom_preview_pos = next((item["previewPos"] for item in custom_previewpos_data if item["id"] == song_id), None) def convert_song(song_id, custom_songs): preview_pos = get_preview_pos(song_id) if custom_songs and custom_preview_pos is not None: song_filename = os.path.join(custom_data_dir, "sound", f"song_{song_id}.mp3") else: song_filename = os.path.join(data_dir, "sound", f"song_{song_id}.mp3") output_file = os.path.join(audio_output_dir, f"song_{song_id}.nus3bank") command = [ "python", "conv.py", song_filename, "at9", platform_tag, str(preview_pos), # Convert preview_pos to string song_id ] subprocess.run(command) if os.path.exists(f"song_{song_id}.nus3bank"): shutil.move(f"song_{song_id}.nus3bank", output_file) print(f"Created {output_file} successfully.") else: print(f"Conversion failed for song_{song_id}.") if os.path.exists(f"song_{song_id}.mp3.at9"): os.remove(f"song_{song_id}.mp3.at9") print(f"Deleted song_{song_id}.mp3.at9") # Check if preview_pos or custom_preview_pos is not None and run conversion if preview_pos is not None or (custom_songs and custom_preview_pos is not None): convert_song(song_id, custom_songs) elif game_platform == "PTB": # Find the corresponding preview position for the current song_id preview_pos = next((item["previewPos"] for item in previewpos_data if item["id"] == song_id), None) if custom_songs: custom_preview_pos = next((item["previewPos"] for item in custom_previewpos_data if item["id"] == song_id), None) def convert_song(song_id, custom_songs): preview_pos = get_preview_pos(song_id) if custom_songs and custom_preview_pos is not None: song_filename = os.path.join(custom_data_dir, "sound", f"song_{song_id}.mp3") else: song_filename = os.path.join(data_dir, "sound", f"song_{song_id}.mp3") output_file = os.path.join(audio_output_dir, f"song_{song_id}.bin") command = [ "python", "script/acb/acb.py", song_filename, song_id ] subprocess.run(command) shutil.move(f"song_{song_id}.bin", output_file) # Check if preview_pos or custom_preview_pos is not None and run conversion if preview_pos is not None or (custom_songs and custom_preview_pos is not None): convert_song(song_id, custom_songs) elif game_platform == "NS1": # Find the corresponding preview position for the current song_id preview_pos = next((item["previewPos"] for item in previewpos_data if item["id"] == song_id), None) if custom_songs: custom_preview_pos = next((item["previewPos"] for item in custom_previewpos_data if item["id"] == song_id), None) def convert_song(song_id, custom_songs): preview_pos = get_preview_pos(song_id) if custom_songs and custom_preview_pos is not None: song_filename = os.path.join(custom_data_dir, "sound", f"song_{song_id}.mp3") else: song_filename = os.path.join(data_dir, "sound", f"song_{song_id}.mp3") output_file = os.path.join(audio_output_dir, f"song_{song_id}.nus3bank") command = [ "python", "conv.py", song_filename, "idsp", platform_tag, str(preview_pos), # Convert preview_pos to string song_id ] subprocess.run(command) if os.path.exists(f"song_{song_id}.nus3bank"): shutil.move(f"song_{song_id}.nus3bank", output_file) print(f"Created {output_file} successfully.") else: print(f"Conversion failed for song_{song_id}.") if os.path.exists(f"song_{song_id}.mp3.idsp"): os.remove(f"song_{song_id}.mp3.idsp") print(f"Deleted song_{song_id}.mp3.idsp") # Check if preview_pos or custom_preview_pos is not None and run conversion if preview_pos is not None or (custom_songs and custom_preview_pos is not None): convert_song(song_id, custom_songs) # Export selected musicinfo and wordlist if game_platform == "PTB": selected_musicinfo_path = os.path.join(output_dir, musicinfo_filename) selected_wordlist_path = os.path.join(output_dir, "wordlist.json") selected_songinfo_path = os.path.join(output_dir, songinfo_filename) with open(selected_songinfo_path, "w", encoding="utf-8") as out_musicinfo_file: json.dump({"items": selected_song_info}, out_musicinfo_file, ensure_ascii=False, indent=4) with open(selected_musicinfo_path, "w", encoding="utf-8") as out_musicinfo_file: json.dump({"items": selected_music_info}, out_musicinfo_file, ensure_ascii=False, indent=4) with open(selected_wordlist_path, "w", encoding="utf-8") as out_wordlist_file: json.dump({"items": selected_wordlist}, out_wordlist_file, ensure_ascii=False, indent=4) merge_ptb() #Compress each ReadAsset file gzip_compress_file(selected_musicinfo_path) gzip_compress_file(selected_wordlist_path) gzip_compress_file(selected_songinfo_path) #Compress each Remove the json files os.remove(selected_musicinfo_path) os.remove(selected_wordlist_path) os.remove(selected_songinfo_path) #Compressed File definitions compressed_musicinfo_path = os.path.join(output_dir, "musicinfo.gz") compressed_wordlist_path = os.path.join(output_dir, "wordlist.gz") compressed_songinfo_path = os.path.join(output_dir, "songinfo.gz") # Final Output definitions final_musicinfo = os.path.join(output_dir, "musicinfo.bin") final_wordlist = os.path.join(output_dir, "wordlist.bin") final_songinfo = os.path.join(output_dir, "songinfo.bin") # Encrypt the final files encrypt_file_ptb(compressed_musicinfo_path, final_musicinfo) encrypt_file_ptb(compressed_wordlist_path, final_wordlist) encrypt_file_ptb(compressed_songinfo_path, final_songinfo) # Remove compressed .gz files os.remove(compressed_musicinfo_path) os.remove(compressed_wordlist_path) os.remove(compressed_songinfo_path) elif game_platform == "PS4": selected_musicinfo_path = os.path.join(output_dir, musicinfo_filename) selected_wordlist_path = os.path.join(output_dir, "wordlist.json") with open(selected_musicinfo_path, "w", encoding="utf-8") as out_musicinfo_file: json.dump({"items": selected_music_info}, out_musicinfo_file, ensure_ascii=False, indent=4) with open(selected_wordlist_path, "w", encoding="utf-8") as out_wordlist_file: json.dump({"items": selected_wordlist}, out_wordlist_file, ensure_ascii=False, indent=4) if game_region == "JPN/ASIA": merge_ps4_jp() elif game_region == "EU/USA": merge_ps4_int() #Compress each datatable file gzip_compress_file_ps4(selected_musicinfo_path) gzip_compress_file_ps4(selected_wordlist_path) #Remove .json files os.remove(selected_musicinfo_path) os.remove(selected_wordlist_path) copy_folder(fumen_output_dir,fumen_hitwide_output_dir) elif game_platform == "NS1": selected_musicinfo_path = os.path.join(output_dir, musicinfo_filename) selected_wordlist_path = os.path.join(output_dir, "wordlist.json") with open(selected_musicinfo_path, "w", encoding="utf-8") as out_musicinfo_file: json.dump({"items": selected_music_info}, out_musicinfo_file, ensure_ascii=False, indent=4) with open(selected_wordlist_path, "w", encoding="utf-8") as out_wordlist_file: json.dump({"items": selected_wordlist}, out_wordlist_file, ensure_ascii=False, indent=4) if game_region == "JPN/ASIA": merge_ns1_jp() elif game_region == "EU/USA": merge_ns1_int() #Compress each datatable file gzip_compress_file(selected_musicinfo_path) gzip_compress_file(selected_wordlist_path) #Compress each Remove the json files os.remove(selected_musicinfo_path) os.remove(selected_wordlist_path) #Compressed File definitions compressed_musicinfo_path = os.path.join(output_dir, "musicinfo.gz") compressed_wordlist_path = os.path.join(output_dir, "wordlist.gz") # Final Output definitions final_musicinfo = os.path.join(output_dir, "musicinfo.bin") final_wordlist = os.path.join(output_dir, "wordlist.bin") # Encrypt the final files encrypt_file_ns1(compressed_musicinfo_path, final_musicinfo) encrypt_file_ns1(compressed_wordlist_path, final_wordlist) # Remove compressed .gz files os.remove(compressed_musicinfo_path) os.remove(compressed_wordlist_path) copy_folder(fumen_output_dir,fumen_hitwide_output_dir) copy_folder(fumen_output_dir,fumen_hitnarrow_output_dir) messagebox.showinfo("Export Completed", "Selected songs exported successfully!") except Exception as e: messagebox.showerror("Export Error", f"An error occurred during export: {str(e)}") #Button shenanigans, because the order they appear on the gui, is determined by the literal order they are in the code??? # Top Side if lang == "jp": preview_button = ttk.Button(main_frame, text="オーディオ・プレビュー", command=preview_selected) else: preview_button = ttk.Button(main_frame, text="Preview", command=preview_selected) preview_button.pack(side="top", padx=20, pady=10) # Create sorting options if lang == "jp": sort_options = ["ID", "曲", "ジャンル順"] sort_label = tk.Label(main_frame, text="ソートフィルター:") else: sort_options = ["ID", "Song Name", "Genre"] sort_label = tk.Label(main_frame, text="Sort by:") sort_label.pack(side="top", padx=20, pady=5) sort_var = tk.StringVar(main_frame) sort_var.set("ID") sort_menu = tk.OptionMenu(main_frame, sort_var, *sort_options, command=lambda _: sort_tree(sort_var.get())) sort_menu.pack(side="top", padx=20, pady=0) search_entry.pack(side="top", padx=20, pady=10, fill="x") # search bar, currently broken # Bottom Side if lang == "jp": export_button = ttk.Button(main_frame, text="エクスポート", command=export_data) else: export_button = ttk.Button(main_frame, text="Export", command=export_data) export_button.pack(side="bottom", padx=20, pady=10) # Create Selection Count Label selection_count_label = ttk.Label(main_frame, text="0/???") selection_count_label.pack(side="bottom", padx=20, pady=10) game_platform_var = tk.StringVar(main_frame) game_platform_var.set("PS4") game_platform_choices = ["PS4", "NS1", "PTB"] game_platform_menu = tk.OptionMenu(main_frame, game_platform_var, *game_platform_choices) game_platform_menu.pack(side="bottom", padx=20, pady=0) # Create Label for Platform selection if lang == "jp": platform_label = tk.Label(main_frame, text="ゲーム機:") else: platform_label = tk.Label(main_frame, text="Platform") platform_label.pack(side="bottom", padx=20, pady=5) # Game region selection, needed for wordlist export. game_region_var = tk.StringVar(main_frame) game_region_var.set("JPN/ASIA") game_region_choices = ["JPN/ASIA", "EU/USA"] game_region_menu = tk.OptionMenu(main_frame, game_region_var, *game_region_choices) game_region_menu.pack(side="bottom", padx=20, pady=10) if lang == "jp": game_region_label = tk.Label(main_frame, text="ゲーム地域:") else: game_region_label = tk.Label(main_frame, text="Game Region:") game_region_label.pack(side="bottom", padx=20, pady=0) # Doesn't function? # Update selection count when tree selection changes #tree.bind("<>", lambda event: update_selection_count()) window.mainloop()