Update and Improve tool

This commit is contained in:
Cainan 2024-06-24 02:34:59 +01:00
parent cfe264baf5
commit c15f9450ad
3 changed files with 224 additions and 95 deletions

View File

@ -7,7 +7,7 @@ Nintendo Switch Version / Drum 'n' Fun v1.4.13 (Nintendo Switch)
Drum Session (Any Update) (PlayStation 4) Drum Session (Any Update) (PlayStation 4)
Pop Tap Beat (Any Update) (iOS/MacOS/Apple TV) Pop Tap Beat (Any Update) (iOS/MacOS/Apple TV)
A version of this tool with all song data can be found elsewhere. A version of this tool with all song data can be found elsewhere.
There's 3 options to sort songs by: ID (A-Z), Song Name (A-Z) and Genre There's 3 options to sort songs by: ID (A-Z), Song Name (A-Z) and Genre
This is still a work in-progress, so please report any issues found to me, along with suggestions for features or game support. This is still a work in-progress, so please report any issues found to me, along with suggestions for features or game support.
@ -25,6 +25,9 @@ Due to copyright reasons, etc. no song data will be provided with this tool, how
Currently, due to the nature of this relying on some Windows executables, this tool currently only supports Windows. Currently, due to the nature of this relying on some Windows executables, this tool currently only supports Windows.
I will be looking into getting it running on Unix-based operating systems. (Linux/macOS) I will be looking into getting it running on Unix-based operating systems. (Linux/macOS)
# Additional Features
This tool now supports the ability to load songs from an additional folder. This is designed to allow both Official song data and Custom Song data to be used in-conjunction with eachother.
![song conversion tool](https://i.imgur.com/TnRlAxR.png) ![song conversion tool](https://i.imgur.com/TnRlAxR.png)
## Tools Used ## Tools Used

View File

@ -1,3 +1,5 @@
{ {
"max_concurrent": 25 "max_concurrent": 25,
"custom_songs": false,
"custom_song_path": "data_custom/"
} }

View File

@ -11,11 +11,43 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives import padding
# 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
"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/" data_dir = "data/"
musicinfo_path = os.path.join(data_dir, "datatable", "musicinfo.json") musicinfo_path = os.path.join(data_dir, "datatable", "musicinfo.json")
wordlist_path = os.path.join(data_dir, "datatable", "wordlist.json") wordlist_path = os.path.join(data_dir, "datatable", "wordlist.json")
previewpos_path = os.path.join(data_dir, "datatable", "previewpos.json") previewpos_path = os.path.join(data_dir, "datatable", "previewpos.json")
# Load configuration
config = load_config()
custom_songs = config["custom_songs"]
# custom_song_path = config["custom_path"]
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 = {} item_selection_state = {}
with open(musicinfo_path, "r", encoding="utf-8") as musicinfo_file: with open(musicinfo_path, "r", encoding="utf-8") as musicinfo_file:
@ -24,6 +56,13 @@ with open(musicinfo_path, "r", encoding="utf-8") as musicinfo_file:
with open(wordlist_path, "r", encoding="utf-8") as wordlist_file: with open(wordlist_path, "r", encoding="utf-8") as wordlist_file:
word_list = json.load(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)
genre_map = { genre_map = {
0: ("POP", "light blue"), 0: ("POP", "light blue"),
1: ("Anime", "orange"), 1: ("Anime", "orange"),
@ -38,6 +77,10 @@ genre_map = {
song_titles = {item["key"]: item["englishUsText"] for item in word_list["items"]} song_titles = {item["key"]: item["englishUsText"] for item in word_list["items"]}
song_subtitles = {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:
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 = tk.Tk()
window.title("Taiko no Tatsujin Song Conversion GUI Tool") window.title("Taiko no Tatsujin Song Conversion GUI Tool")
@ -73,26 +116,6 @@ vsb.pack(side="left", fill="y", padx=(0, 10), pady=10)
selection_count = tk.IntVar() selection_count = tk.IntVar()
selection_count.set(0) # Initial selection count selection_count.set(0) # Initial selection count
# 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
}
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
# Load configuration
config = load_config()
def on_search_keyrelease(event): def on_search_keyrelease(event):
print("Key released:", event.keysym) print("Key released:", event.keysym)
#filter_treeview() #filter_treeview()
@ -170,6 +193,39 @@ def populate_tree():
item_id = tree.insert("", "end", values=("", unique_id, song_id, english_title, english_subtitle, genre_name, difficulty_info)) 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) tree.tag_configure(genre_name, background=genre_color)
if custom_songs == True:
for song in sorted(custom_music_info["items"], key=lambda x: x["id"]): # Sort by ID
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 = 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)
# Check if the search text matches the song name
if search_var.get().strip().lower() in english_title.lower():
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)
# Restore original selection after filtering # Restore original selection after filtering
for item in current_selection: for item in current_selection:
if tree.exists(item): # Check if item exists in Treeview if tree.exists(item): # Check if item exists in Treeview
@ -191,12 +247,19 @@ def sort_tree(sort_option):
selection_count.set(0) # Reset Counter to 0 selection_count.set(0) # Reset Counter to 0
for song in sorted(music_info["items"], key=lambda x: song_titles.get(f"song_{x['id']}", "-")): for song in sorted(music_info["items"], key=lambda x: song_titles.get(f"song_{x['id']}", "-")):
populate_song_entry(song) populate_song_entry(song)
if custom_songs == True:
for song in sorted(custom_music_info["items"], key=lambda x: custom_song_titles.get(f"song_{x['id']}", "-")):
populate_song_entry(song)
elif sort_option == "Genre": elif sort_option == "Genre":
selection_count.set(0) # Reset Counter to 0 selection_count.set(0) # Reset Counter to 0
for genre_no in sorted(genre_map.keys()): for genre_no in sorted(genre_map.keys()):
for song in sorted(music_info["items"], key=lambda x: x["id"]): for song in sorted(music_info["items"], key=lambda x: x["id"]):
if song["genreNo"] == genre_no: if song["genreNo"] == genre_no:
populate_song_entry(song) populate_song_entry(song)
if custom_songs == True:
for song in sorted(custom_music_info["items"], key=lambda x: x["id"]):
if song["genreNo"] == genre_no:
populate_song_entry(song)
def populate_song_entry(song): def populate_song_entry(song):
unique_id = "" unique_id = ""
@ -205,6 +268,9 @@ def populate_song_entry(song):
genre_name, genre_color = genre_map.get(genre_no, ("Unknown Genre", "white")) genre_name, genre_color = genre_map.get(genre_no, ("Unknown Genre", "white"))
english_title = song_titles.get(f"song_{song_id}", "-") english_title = song_titles.get(f"song_{song_id}", "-")
english_subtitle = song_subtitles.get(f"song_sub_{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_easy = song.get("starEasy", "N/A")
star_normal = song.get("starNormal", "N/A") star_normal = song.get("starNormal", "N/A")
@ -261,17 +327,35 @@ tree.bind("<Button-1>", toggle_checkbox)
def preview_audio(song_id): def preview_audio(song_id):
preview_pos = get_preview_pos(song_id) preview_pos = get_preview_pos(song_id)
if preview_pos is not None: if preview_pos is not None:
song_filename = f"data/sound/song_{song_id}.mp3" song_filename = os.path.join(data_dir, "sound", f"song_{song_id}.mp3")
subprocess.run(["ffplay", "-autoexit", "-ss", f"{preview_pos / 1000}", song_filename]) 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): def get_preview_pos(song_id):
# Load previewpos data from the default file
with open(previewpos_path, "r", encoding="utf-8") as previewpos_file: with open(previewpos_path, "r", encoding="utf-8") as previewpos_file:
previewpos_data = json.load(previewpos_file) previewpos_data = json.load(previewpos_file)
for item in previewpos_data: for item in previewpos_data:
if item["id"] == song_id: if item["id"] == song_id:
return item["previewPos"] 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 return None
def preview_selected(): def preview_selected():
selected_item = tree.selection() selected_item = tree.selection()
if selected_item: if selected_item:
@ -493,6 +577,10 @@ def export_data():
# Load preview position data # Load preview position data
with open(previewpos_path, "r", encoding="utf-8") as previewpos_file: with open(previewpos_path, "r", encoding="utf-8") as previewpos_file:
previewpos_data = json.load(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 # Copy fumen folders for selected songs to output directory
for item_id in selected_items: for item_id in selected_items:
@ -503,9 +591,23 @@ def export_data():
song_info = next((item for item in music_info["items"] if item["id"] == song_id), None) 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: for item_id in selected_items:
song_id = tree.item(item_id)["values"][2] song_id = tree.item(item_id)["values"][2]
song_info = next((item for item in music_info["items"] if item["id"] == song_id), None) 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: if song_info:
@ -703,84 +805,106 @@ def export_data():
if game_platform == "PS4": if game_platform == "PS4":
# Find the corresponding preview position for the current song_id # 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) preview_pos = next((item["previewPos"] for item in previewpos_data if item["id"] == song_id), None)
if preview_pos is not None: if custom_songs:
# Run the audio conversion command based on the game platform custom_preview_pos = next((item["previewPos"] for item in custom_previewpos_data if item["id"] == song_id), None)
def convert_song(song_id):
preview_pos = get_preview_pos(song_id) def convert_song(song_id, custom_songs):
song_filename = f"data/sound/song_{song_id}.mp3" preview_pos = get_preview_pos(song_id)
output_file = os.path.join(audio_output_dir, f"song_{song_id}.nus3bank") if custom_songs and custom_preview_pos is not None:
command = [ song_filename = os.path.join(custom_data_dir, "sound", f"song_{song_id}.mp3")
"python", else:
"conv.py", song_filename = os.path.join(data_dir, "sound", f"song_{song_id}.mp3")
song_filename,
"at9", output_file = os.path.join(audio_output_dir, f"song_{song_id}.nus3bank")
platform_tag, command = [
str(preview_pos), # Convert preview_pos to string "python",
song_id "conv.py",
] song_filename,
subprocess.run(command) "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) 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")
else:
print(f"Error: File song_{song_id}.mp3.at9 not found.")
# 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": elif game_platform == "NS1":
# Find the corresponding preview position for the current song_id # 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) preview_pos = next((item["previewPos"] for item in previewpos_data if item["id"] == song_id), None)
if preview_pos is not None: if custom_songs:
# Run the audio conversion command based on the game platform custom_preview_pos = next((item["previewPos"] for item in custom_previewpos_data if item["id"] == song_id), None)
def convert_song(song_id):
preview_pos = get_preview_pos(song_id) def convert_song(song_id, custom_songs):
song_filename = f"data/sound/song_{song_id}.mp3" preview_pos = get_preview_pos(song_id)
output_file = os.path.join(audio_output_dir, f"song_{song_id}.nus3bank") if custom_songs and custom_preview_pos is not None:
command = [ song_filename = os.path.join(custom_data_dir, "sound", f"song_{song_id}.mp3")
"python", else:
"conv.py", song_filename = os.path.join(data_dir, "sound", f"song_{song_id}.mp3")
song_filename,
"idsp", output_file = os.path.join(audio_output_dir, f"song_{song_id}.nus3bank")
platform_tag, command = [
str(preview_pos), # Convert preview_pos to string "python",
song_id "conv.py",
] song_filename,
subprocess.run(command) "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) 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")
else:
print(f"Error: File song_{song_id}.mp3.idsp not found.")
elif game_platform == "PTB": # Check if preview_pos or custom_preview_pos is not None and run conversion
# Find the corresponding preview position for the current song_id if preview_pos is not None or (custom_songs and custom_preview_pos is not None):
preview_pos = next((item["previewPos"] for item in previewpos_data if item["id"] == song_id), None) convert_song(song_id, custom_songs)
if preview_pos is not None:
# Run the audio conversion command based on the game platform
def convert_song(song_id):
preview_pos = get_preview_pos(song_id)
song_filename = f"data/sound/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)
try:
if len(selected_items) > 0:
with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrent) as executor:
futures = []
for item_id in selected_items:
song_id = tree.item(item_id)["values"][2]
if song_id not in processed_ids:
# Submit conversion task for this song ID
futures.append(executor.submit(convert_song, song_id))
processed_ids.add(song_id) # Mark as processed
# Wait for all tasks to complete
concurrent.futures.wait(futures)
else:
messagebox.showinfo("No Songs Selected", "Please select songs to export.")
except Exception as e:
messagebox.showerror("Export Error", f"An error occurred during export: {str(e)}")
# Export selected musicinfo and wordlist # Export selected musicinfo and wordlist
if game_platform == "PTB": if game_platform == "PTB":