Overhaul nus3bank tool

This commit is contained in:
Cainan 2024-05-06 00:14:36 +01:00
parent dd717c5e84
commit d2c5151742
33 changed files with 671 additions and 148 deletions

View File

@ -1,11 +1,52 @@
# Taiko no Tatsujin - Simple Nus3bank Creation Tool
# Taiko no Tatsujin - Nus3bank Creation Tool
Simple Python 3 script to allows you to quickly create nus3bank files and specify in-game audio preview points.
Python 3 scripts that converts audio to a Taiko no Tatsujin compatable `.nus3bank` file.
Accepted file types: `.mp3`, `.wav`, `.flac` and whatever else pydub supports.
`Usage: nus3.py <audio_file> <preview_point> <output_file>`
```
usage: conv.py [-h] [input_audio] [audio_type] [game] [preview_point] [song_id]
Only supports specific output file names, adheering to the game's ID system.
E.g: `song_abs.nus3bank` up to `song_fungus.nus3bank`
Convert audio to nus3bank
By default includes template files for Nijiiro.
`nus3.py` can be modified along with the templates to specifiy offsets for other `.nus3bank` files
positional arguments:
input_audio Input audio file path.
audio_type Type of input audio (e.g., bnsf, at9, idsp, lopus).
game Game type (e.g., nijiiro, ns1, ps4).
preview_point Audio preview point in ms.
song_id Song ID for the nus3bank file.
```
By default includes support for Taiko no Tatsujin Wii U 3, NS1, PS4 and Nijiiro.
Support for other Taiko no Tatsujin games that use `.nus3bank` can be added in the future.
### Prerequisites
[Python 3.12.3](https://www.python.org/downloads/) or newer installed.
Python 3 Module pydub `pip install pydub`
### Supported Audio Formats
| Audio Format | NS1 | PS4 | WIIU3 | Nijiiro |
| ------------- | ------------- | ------------- | ------------- | ------------- |
| WAV (PCM) | ✅ | ✅ | ✅ | ✅ |
| BNSF (IS14) | ✅ | ✅ | ❓ | ✅ |
| Nintendo OPUS | ✅ | ❌ | ❌ | ❌ |
| Nintendo IDSP | ✅ | ❌ | ✅ | ✅ |
| Sony AT9 | ❌ | ✅ | ❌ | ❌ |
### Known Limitations
It seems that if a IS14 BNSF .nus3bank file is too long/too large in size, then it'll fail to play in "Song Select", even if the "Preview Point" is properly set.
When it comes to Song ID:
Nijiiro can have Song IDs ranging from 3 to 8 characters.
Wii U, PS4 and NS1 can only have Song IDs ranging from 3 to 6 characters.
Exceeding this will result in an error.
## Tools Used
at9tool - Used to convert audio to the Sony AT9 format.
[VGAudioCli](https://github.com/Thealexbarney/VGAudio) - Used to convert audio to Nintendo IDSP and Nintendo OPUS.
[G.722.1 Reference Tool](https://www.itu.int/rec/T-REC-G.722.1-200505-I/en) - Used to convert audio to Polycom Siren 14
### Special Thanks
Steam User [descatal](https://steamcommunity.com/id/descatal) for writing [this](https://exvsfbce.home.blog/2020/02/04/guide-to-encoding-bnsf-is14-audio-files-converting-wav-back-to-bnsf-is14/) guide on how to create/encode `bnsf` files.
[korenkonder](https://github.com/korenkonder) for compiling the G.722.1 tool used in this project.
[Kamui/despairoharmony](https://github.com/despairoharmony) for some of the Nijiiro `.nus3bank` template research.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

70
TaikoNus3bankMake/conv.py Normal file
View File

@ -0,0 +1,70 @@
import argparse
import subprocess
import os
import sys
def convert_audio_to_nus3bank(input_audio, audio_type, game, preview_point, song_id):
# Determine the output filename for the nus3bank
output_filename = f"song_{song_id}.nus3bank"
converted_audio_file = f"{input_audio}.{audio_type}"
# Determine the path to the run.py script within the 'script' folder
templates_folder = os.path.join(os.path.dirname(__file__), 'script')
run_py_path = os.path.join(templates_folder, 'run.py')
# Prepare the command based on the audio type
if audio_type in ["bnsf", "at9", "idsp", "lopus", "wav"]:
# Construct the command to convert input audio to the specified type
conversion_command = ["python", run_py_path, audio_type, input_audio, f"{input_audio}.{audio_type}"]
# Construct the command to create the nus3bank
nus3_command = ["python", run_py_path, "nus3", game, f"{input_audio}.{audio_type}", str(preview_point), output_filename]
try:
# Execute the conversion command
subprocess.run(conversion_command, check=True)
# Execute the nus3 command
subprocess.run(nus3_command, check=True)
print(f"Conversion successful! Created {output_filename}")
# Delete the non-nus3bank file after successful conversion
if os.path.exists(converted_audio_file):
os.remove(converted_audio_file)
print(f"Deleted {converted_audio_file}")
except subprocess.CalledProcessError as e:
print(f"Error: {e}")
else:
print(f"Unsupported audio type: {audio_type}")
def main():
# Create an argument parser
parser = argparse.ArgumentParser(description="Convert audio to nus3bank")
# Define command-line arguments
parser.add_argument("input_audio", type=str, nargs="?", help="Input audio file path.")
parser.add_argument("audio_type", type=str, nargs="?", help="Type of input audio (e.g., wav, bnsf, at9, idsp, lopus).")
parser.add_argument("game", type=str, nargs="?", help="Game type (e.g., nijiiro, ns1, ps4, wiiu3).")
parser.add_argument("preview_point", type=int, nargs="?", help="Audio preview point in ms.")
parser.add_argument("song_id", type=str, nargs="?", help="Song ID for the nus3bank file.")
# Parse the command-line arguments
args = parser.parse_args()
# If no arguments are provided, display usage information
if len(sys.argv) == 1:
parser.print_help()
sys.exit(0)
# Validate input audio file path
if not args.input_audio:
print("Error: Input audio file path is required.")
parser.print_help()
sys.exit(1)
# Call function to convert audio to nus3bank
convert_audio_to_nus3bank(args.input_audio, args.audio_type, args.game, args.preview_point, args.song_id)
if __name__ == "__main__":
main()

View File

@ -1,141 +0,0 @@
import sys
import os
import struct
import random
import toml
def generate_random_uint16_hex():
return format(random.randint(0, 65535), '04X')
def load_template_config():
# Load template configurations from config.toml (if needed in the future)
# This function can be expanded to load more template configurations if necessary
# For now, we don't need to use this function directly for selecting templates
return {}
def select_template_name(output_file):
# Determine the appropriate template name based on the length of the output file name
base_filename = os.path.splitext(output_file)[0]
length = len(base_filename)
if length == 8:
return "song_ABC"
elif length == 9:
return "song_ABCD"
elif length == 10:
return "song_ABCDE"
elif length == 11:
return "song_ABCDEF"
elif length == 12:
return "song_ABCDEFG"
elif length == 13:
return "song_ABCDEFGH"
else:
raise ValueError("Output file name length (excluding extension) must be between 8 and 12 characters.")
def modify_nus3bank_template(template_name, audio_file, preview_point, output_file):
# Define template configurations based on the selected template_name
template_configs = {
"song_ABC": {
"unique_id_offset": 176,
"audio_size_offsets": [76, 1568, 1852],
"preview_point_offset": 1724,
"song_placeholder": "song_ABC",
"template_file": "song_ABC.nus3bank"
},
"song_ABCD": {
"unique_id_offset": 176,
"audio_size_offsets": [76, 1568, 1852],
"preview_point_offset": 1724,
"song_placeholder": "song_ABCD",
"template_file": "song_ABCD.nus3bank"
},
"song_ABCDE": {
"unique_id_offset": 176,
"audio_size_offsets": [76, 1568, 1852],
"preview_point_offset": 1724,
"song_placeholder": "song_ABCDE",
"template_file": "song_ABCDE.nus3bank"
},
"song_ABCDEF": {
"unique_id_offset": 180,
"audio_size_offsets": [76, 1576, 1868],
"preview_point_offset": 1732,
"song_placeholder": "song_ABCDEF",
"template_file": "song_ABCDEF.nus3bank"
},
"song_ABCDEFG": {
"unique_id_offset": 180,
"audio_size_offsets": [76, 1672, 1964],
"preview_point_offset": 1824,
"song_placeholder": "song_ABCDEFG",
"template_file": "song_ABCDEFG.nus3bank"
},
"song_ABCDEFGH": {
"unique_id_offset": 180,
"audio_size_offsets": [76, 1576, 1868],
"preview_point_offset": 1732,
"song_placeholder": "song_ABCDEFGH",
"template_file": "song_ABCDEFGH.nus3bank"
}
}
# Retrieve template configurations for the specified template_name
template_config = template_configs[template_name]
# Read template nus3bank file from the templates folder
template_file = os.path.join("templates", template_config['template_file'])
with open(template_file, 'rb') as f:
template_data = bytearray(f.read())
# Generate random UInt16 hex for unique ID
unique_id_hex = generate_random_uint16_hex()
# Set unique ID in the template data at the specified offset
template_data[template_config['unique_id_offset']:template_config['unique_id_offset']+2] = bytes.fromhex(unique_id_hex)
# Get size of the audio file in bytes
audio_size = os.path.getsize(audio_file)
# Convert audio size to UInt32 bytes in little-endian format
size_bytes = audio_size.to_bytes(4, 'little')
# Set audio size in the template data at the specified offsets
for offset in template_config['audio_size_offsets']:
template_data[offset:offset+4] = size_bytes
# Convert preview point (milliseconds) to UInt32 bytes in little-endian format
preview_point_ms = int(preview_point)
preview_point_bytes = preview_point_ms.to_bytes(4, 'little')
# Set preview point in the template data at the specified offset
template_data[template_config['preview_point_offset']:template_config['preview_point_offset']+4] = preview_point_bytes
# Replace song name placeholder with the output file name in bytes
output_file_bytes = output_file.encode('utf-8')
template_data = template_data.replace(template_config['song_placeholder'].encode('utf-8'), output_file_bytes.replace(b'.nus3bank', b''))
# Append the audio file contents to the modified template data
with open(audio_file, 'rb') as audio:
template_data += audio.read()
# Write the modified data to the output file
with open(output_file, 'wb') as out:
out.write(template_data)
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: audio.py <audio_file> <preview_point> <output_file>")
sys.exit(1)
audio_file = sys.argv[1]
preview_point = sys.argv[2]
output_file = sys.argv[3]
try:
template_name = select_template_name(output_file)
modify_nus3bank_template(template_name, audio_file, preview_point, output_file)
print(f"Created {output_file} successfully.")
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)

View File

@ -0,0 +1,44 @@
import subprocess
import os
import sys
import shutil
from pydub import AudioSegment
def convert_audio_to_at9(input_file, output_file):
# Create a temporary folder to store intermediate files
temp_folder = "temp"
os.makedirs(temp_folder, exist_ok=True)
try:
# Check if the input file is already in WAV format
if not input_file.lower().endswith('.wav'):
# Load the input audio file using pydub and convert to WAV
temp_wav_file = os.path.join(temp_folder, "temp.wav")
audio = AudioSegment.from_file(input_file)
audio.export(temp_wav_file, format="wav")
input_file = temp_wav_file
# Path to AT9Tool executable
at9tool_cli_path = os.path.join("bin", "at9tool.exe")
# Run VGAudioCli to convert WAV to AT9
subprocess.run([at9tool_cli_path, "-e", "-br", "192", input_file, output_file], check=True)
finally:
# Clean up temporary folder
shutil.rmtree(temp_folder, ignore_errors=True)
if __name__ == "__main__":
# Check command-line arguments
if len(sys.argv) != 3:
print("Usage: python at9.py <input_file> <output_file>")
sys.exit(1)
input_audio_file = sys.argv[1]
output_audio_file = sys.argv[2]
try:
convert_audio_to_at9(input_audio_file, output_audio_file)
print(f"Conversion successful. Output file: {output_audio_file}")
except Exception as e:
print(f"Error during conversion: {e}")

View File

@ -0,0 +1,93 @@
import subprocess
import os
import sys
import shutil
from pydub import AudioSegment
from pydub.exceptions import CouldntDecodeError
def convert_to_mono_48k(input_file, output_file):
"""Convert input audio file to 16-bit mono WAV with 48000 Hz sample rate."""
try:
audio = AudioSegment.from_file(input_file)
audio = audio.set_channels(1) # Convert to mono
audio = audio.set_frame_rate(48000) # Set frame rate to 48000 Hz
audio = audio.set_sample_width(2) # Set sample width to 16-bit (2 bytes)
audio.export(output_file, format='wav')
except CouldntDecodeError:
print(f"Error: Unable to decode {input_file}. Please provide a valid audio file.")
sys.exit(1)
def run_encode_tool(input_wav, output_bs):
"""Run external encode tool with specified arguments."""
subprocess.run(['bin/encode.exe', '0', input_wav, output_bs, '48000', '14000'])
def modify_bnsf_template(output_bs, output_bnsf, header_size, total_samples):
"""Modify the BNSF template file with calculated values and combine with output.bs."""
# Calculate the file size of output.bs
bs_file_size = os.path.getsize(output_bs)
# Create modified BNSF data
new_file_size = bs_file_size + header_size - 0x8
total_samples_bytes = total_samples.to_bytes(4, 'big')
bs_file_size_bytes = bs_file_size.to_bytes(4, 'big')
# Read BNSF template data
with open('templates/header.bnsf', 'rb') as template_file:
bnsf_template_data = bytearray(template_file.read())
# Modify BNSF template with calculated values
bnsf_template_data[0x4:0x8] = new_file_size.to_bytes(4, 'big') # File size
bnsf_template_data[0x1C:0x20] = total_samples_bytes # Total sample count
bnsf_template_data[0x2C:0x30] = bs_file_size_bytes # Size of output.bs
# Append output.bs data to modified BNSF template
with open(output_bs, 'rb') as bs_file:
bs_data = bs_file.read()
final_bnsf_data = bnsf_template_data + bs_data
# Write final BNSF file
with open(output_bnsf, 'wb') as output_file:
output_file.write(final_bnsf_data)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: bnsf.py <input_audio> [<output_bnsf>]")
sys.exit(1)
input_audio = sys.argv[1]
output_bnsf = sys.argv[2] if len(sys.argv) > 2 else 'output.bnsf'
# Create temp folder if it doesn't exist
temp_folder = 'temp'
os.makedirs(temp_folder, exist_ok=True)
# Temporary file paths
output_wav = os.path.join(temp_folder, 'output_mono.wav')
output_bs = os.path.join(temp_folder, 'output.bs')
# Header size (assuming fixed size)
header_size = 0x30
try:
# Step 1: Convert input audio to required format (WAV)
convert_to_mono_48k(input_audio, output_wav)
# Step 2: Run external encoding tool
run_encode_tool(output_wav, output_bs)
# Step 3: Get sample count from the converted mono WAV
mono_wav = AudioSegment.from_wav(output_wav)
total_samples = len(mono_wav.get_array_of_samples())
# Step 4: Modify BNSF template with calculated values and combine with output.bs
modify_bnsf_template(output_bs, output_bnsf, header_size, total_samples)
print("BNSF file created:", output_bnsf)
finally:
# Cleanup: Delete temporary files and temp folder
if os.path.exists(temp_folder):
shutil.rmtree(temp_folder)

View File

@ -0,0 +1,44 @@
import subprocess
import os
import sys
import shutil
from pydub import AudioSegment
def convert_audio_to_idsp(input_file, output_file):
# Create a temporary folder to store intermediate files
temp_folder = "temp"
os.makedirs(temp_folder, exist_ok=True)
try:
# Check if the input file is already in WAV format
if not input_file.lower().endswith('.wav'):
# Load the input audio file using pydub and convert to WAV
temp_wav_file = os.path.join(temp_folder, "temp.wav")
audio = AudioSegment.from_file(input_file)
audio.export(temp_wav_file, format="wav")
input_file = temp_wav_file
# Path to VGAudioCli executable
vgaudio_cli_path = os.path.join("bin", "VGAudioCli.exe")
# Run VGAudioCli to convert WAV to IDSP
subprocess.run([vgaudio_cli_path, "-i", input_file, "-o", output_file], check=True)
finally:
# Clean up temporary folder
shutil.rmtree(temp_folder, ignore_errors=True)
if __name__ == "__main__":
# Check command-line arguments
if len(sys.argv) != 3:
print("Usage: python idsp.py <input_file> <output_file>")
sys.exit(1)
input_audio_file = sys.argv[1]
output_audio_file = sys.argv[2]
try:
convert_audio_to_idsp(input_audio_file, output_audio_file)
print(f"Conversion successful. Output file: {output_audio_file}")
except Exception as e:
print(f"Error during conversion: {e}")

View File

@ -0,0 +1,45 @@
import subprocess
import os
import sys
import shutil
from pydub import AudioSegment
def convert_audio_to_opus(input_file, output_file):
# Create a temporary folder to store intermediate files
temp_folder = "temp"
os.makedirs(temp_folder, exist_ok=True)
try:
# Check if the input file is already in WAV format
if not input_file.lower().endswith('.wav'):
# Load the input audio file using pydub and convert to WAV
temp_wav_file = os.path.join(temp_folder, "temp.wav")
audio = AudioSegment.from_file(input_file)
audio = audio.set_frame_rate(48000) # Set frame rate to 48000 Hz
audio.export(temp_wav_file, format="wav")
input_file = temp_wav_file
# Path to VGAudioCli executable
vgaudio_cli_path = os.path.join("bin", "VGAudioCli.exe")
# Run VGAudioCli to convert WAV to Switch OPUS
subprocess.run([vgaudio_cli_path, "-i", input_file, "-o", output_file, "--opusheader", "namco"], check=True)
finally:
# Clean up temporary folder
shutil.rmtree(temp_folder, ignore_errors=True)
if __name__ == "__main__":
# Check command-line arguments
if len(sys.argv) != 3:
print("Usage: python opus.py <input_file> <output_file>")
sys.exit(1)
input_audio_file = sys.argv[1]
output_audio_file = sys.argv[2]
try:
convert_audio_to_opus(input_audio_file, output_audio_file)
print(f"Conversion successful. Output file: {output_audio_file}")
except Exception as e:
print(f"Error during conversion: {e}")

View File

@ -0,0 +1,272 @@
import sys
import os
import struct
import random
def generate_random_uint16_hex():
return format(random.randint(0, 65535), '04X')
def load_template_config():
# Load template configurations from config.toml (if needed in the future)
# This function can be expanded to load more template configurations if necessary
# For now, we don't need to use this function directly for selecting templates
return {}
def select_template_name(game, output_file):
# Determine the appropriate template name based on the game and the length of the output file name
base_filename = os.path.splitext(output_file)[0]
length = len(base_filename)
if game == "nijiiro":
if length == 8:
return "song_ABC"
elif length == 9:
return "song_ABCD"
elif length == 10:
return "song_ABCDE"
elif length == 11:
return "song_ABCDEF"
elif length == 12:
return "song_ABCDEFG"
elif length == 13:
return "song_ABCDEFGH"
elif game == "ps4":
if length == 8:
return "song_ABC"
elif length == 9:
return "song_ABCD"
elif length == 10:
return "song_ABCDE"
elif length == 11:
return "song_ABCDEF"
elif game == "ns1":
if length == 8:
return "song_ABC"
elif length == 9:
return "song_ABCD"
elif length == 10:
return "song_ABCDE"
elif length == 11:
return "song_ABCDEF"
pass
elif game == "wiiu3":
if length == 8:
return "song_ABC"
elif length == 9:
return "song_ABCD"
elif length == 10:
return "song_ABCDE"
elif length == 11:
return "song_ABCDEF"
pass
raise ValueError("Unsupported game or output file name length.")
def modify_nus3bank_template(game, template_name, audio_file, preview_point, output_file):
# Define game-specific template configurations
game_templates = {
"nijiiro": {
"template_folder": "nijiiro",
"templates": {
"song_ABC": {
"unique_id_offset": 176,
"audio_size_offsets": [76, 1568, 1852],
"preview_point_offset": 1724,
"song_placeholder": "song_ABC",
"template_file": "song_ABC.nus3bank"
},
"song_ABCD": {
"unique_id_offset": 176,
"audio_size_offsets": [76, 1568, 1852],
"preview_point_offset": 1724,
"song_placeholder": "song_ABCD",
"template_file": "song_ABCD.nus3bank"
},
"song_ABCDE": {
"unique_id_offset": 176,
"audio_size_offsets": [76, 1568, 1852],
"preview_point_offset": 1724,
"song_placeholder": "song_ABCDE",
"template_file": "song_ABCDE.nus3bank"
},
"song_ABCDEF": {
"unique_id_offset": 180,
"audio_size_offsets": [76, 1576, 1868],
"preview_point_offset": 1732,
"song_placeholder": "song_ABCDEF",
"template_file": "song_ABCDEF.nus3bank"
},
"song_ABCDEFG": {
"unique_id_offset": 180,
"audio_size_offsets": [76, 1672, 1964],
"preview_point_offset": 1824,
"song_placeholder": "song_ABCDEFG",
"template_file": "song_ABCDEFG.nus3bank"
},
"song_ABCDEFGH": {
"unique_id_offset": 180,
"audio_size_offsets": [76, 1576, 1868],
"preview_point_offset": 1732,
"song_placeholder": "song_ABCDEFGH",
"template_file": "song_ABCDEFGH.nus3bank"
},
}
},
"ns1": {
"template_folder": "ns1",
"templates": {
"song_ABC": {
"audio_size_offsets": [76, 5200, 5420],
"preview_point_offset": 5324,
"song_placeholder": "SONG_ABC",
"template_file": "SONG_ABC.nus3bank"
},
"song_ABCD": {
"audio_size_offsets": [76, 5200, 5420],
"preview_point_offset": 5324,
"song_placeholder": "SONG_ABCD",
"template_file": "SONG_ABCD.nus3bank"
},
"song_ABCDE": {
"audio_size_offsets": [76, 5200, 5404],
"preview_point_offset": 5320,
"song_placeholder": "SONG_ABCDE",
"template_file": "SONG_ABCDE.nus3bank"
},
"song_ABCDEF": {
"audio_size_offsets": [76, 5208, 5420],
"preview_point_offset": 5324,
"song_placeholder": "SONG_ABCDEF",
"template_file": "SONG_ABCDEF.nus3bank"
}
}
},
"ps4": {
"template_folder": "ps4",
"templates": {
"song_ABC": {
"audio_size_offsets": [76, 3220, 3436],
"preview_point_offset": 3344,
"song_placeholder": "SONG_ABC",
"template_file": "SONG_ABC.nus3bank"
},
"song_ABCD": {
"audio_size_offsets": [76, 3220, 3436],
"preview_point_offset": 3344,
"song_placeholder": "SONG_ABCD",
"template_file": "SONG_ABCD.nus3bank"
},
"song_ABCDE": {
"audio_size_offsets": [76, 3220, 3436],
"preview_point_offset": 3344,
"song_placeholder": "SONG_ABCDE",
"template_file": "SONG_ABCDE.nus3bank"
},
"song_ABCDEF": {
"audio_size_offsets": [76, 3228, 3452],
"preview_point_offset": 3360,
"song_placeholder": "SONG_ABCDEF",
"template_file": "SONG_ABCDEF.nus3bank"
}
}
},
"wiiu3": {
"template_folder": "wiiu3",
"templates": {
"song_ABC": {
"audio_size_offsets": [76, 3420, 3612],
"preview_point_offset": 3540,
"song_placeholder": "SONG_ABC",
"template_file": "SONG_ABC.nus3bank"
},
"song_ABCD": {
"audio_size_offsets": [76, 3420, 3612],
"preview_point_offset": 3540,
"song_placeholder": "SONG_ABCD",
"template_file": "SONG_ABCD.nus3bank"
},
"song_ABCDE": {
"audio_size_offsets": [76, 3420, 3612],
"preview_point_offset": 3540,
"song_placeholder": "SONG_ABCDE",
"template_file": "SONG_ABCDE.nus3bank"
},
"song_ABCDEF": {
"audio_size_offsets": [76, 3428, 3612],
"preview_point_offset": 3548,
"song_placeholder": "SONG_ABCDEF",
"template_file": "SONG_ABCDEF.nus3bank"
}
}
},
}
if game not in game_templates:
raise ValueError("Unsupported game.")
templates_config = game_templates[game]
if template_name not in templates_config["templates"]:
raise ValueError(f"Unsupported template for {game}.")
template_config = templates_config["templates"][template_name]
template_folder = templates_config["template_folder"]
# Read template nus3bank file from the specified game's template folder
template_file = os.path.join("templates", template_folder, template_config['template_file'])
with open(template_file, 'rb') as f:
template_data = bytearray(f.read())
# Set unique ID if it exists in the template configuration
if 'unique_id_offset' in template_config:
# Generate random UInt16 hex for unique ID
unique_id_hex = generate_random_uint16_hex()
# Set unique ID in the template data at the specified offset
template_data[template_config['unique_id_offset']:template_config['unique_id_offset']+2] = bytes.fromhex(unique_id_hex)
# Get size of the audio file in bytes
audio_size = os.path.getsize(audio_file)
# Convert audio size to UInt32 bytes in little-endian format
size_bytes = audio_size.to_bytes(4, 'little')
# Set audio size in the template data at the specified offsets
for offset in template_config['audio_size_offsets']:
template_data[offset:offset+4] = size_bytes
# Convert preview point (milliseconds) to UInt32 bytes in little-endian format
preview_point_ms = int(preview_point)
preview_point_bytes = preview_point_ms.to_bytes(4, 'little')
# Set preview point in the template data at the specified offset
template_data[template_config['preview_point_offset']:template_config['preview_point_offset']+4] = preview_point_bytes
# Replace song name placeholder with the output file name in bytes
output_file_bytes = output_file.encode('utf-8')
template_data = template_data.replace(template_config['song_placeholder'].encode('utf-8'), output_file_bytes.replace(b'.nus3bank', b''))
# Append the audio file contents to the modified template data
with open(audio_file, 'rb') as audio:
template_data += audio.read()
# Write the modified data to the output file
with open(output_file, 'wb') as out:
out.write(template_data)
if __name__ == "__main__":
if len(sys.argv) != 5:
print("Usage: nus3.py <game> <audio_file> <preview_point> <output_file>")
sys.exit(1)
game = sys.argv[1]
audio_file = sys.argv[2]
preview_point = sys.argv[3]
output_file = sys.argv[4]
try:
template_name = select_template_name(game, output_file)
modify_nus3bank_template(game, template_name, audio_file, preview_point, output_file)
print(f"Created {output_file} successfully.")
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)

View File

@ -0,0 +1,22 @@
import os
import sys
import subprocess
def run_script(script_name, script_args):
script_path = os.path.join('script', script_name, f'{script_name}.py')
if os.path.exists(script_path):
command = ['python', script_path] + script_args
subprocess.run(command)
else:
print(f"Script '{script_name}' not found.")
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python launcher.py <script_name> [<script_args>]")
sys.exit(1)
script_name = sys.argv[1]
script_args = sys.argv[2:] # Capture all arguments after script_name
run_script(script_name, script_args)

View File

@ -0,0 +1,33 @@
import os
import sys
from pydub import AudioSegment
def convert_audio_to_wav(input_file, output_file):
try:
# Load the input audio file using pydub
audio = AudioSegment.from_file(input_file)
# Ensure the output file has a .wav extension
if not output_file.lower().endswith('.wav'):
output_file += '.wav'
# Export the audio to WAV format
audio.export(output_file, format="wav")
except Exception as e:
raise RuntimeError(f"Error during WAV conversion: {e}")
if __name__ == "__main__":
# Check command-line arguments
if len(sys.argv) != 3:
print("Usage: python audio_converter.py <input_file> <output_file>")
sys.exit(1)
input_audio_file = sys.argv[1]
output_audio_file = sys.argv[2]
try:
convert_audio_to_wav(input_audio_file, output_audio_file)
print(f"Conversion successful. Output file: {output_audio_file}")
except Exception as e:
print(f"Error during conversion: {e}")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.