mirror of
https://github.com/cainan-c/TaikoPythonTools.git
synced 2024-11-12 01:20:52 +01:00
Overhaul nus3bank tool
This commit is contained in:
parent
dd717c5e84
commit
d2c5151742
@ -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.
|
Convert audio to nus3bank
|
||||||
E.g: `song_abs.nus3bank` up to `song_fungus.nus3bank`
|
|
||||||
|
|
||||||
By default includes template files for Nijiiro.
|
positional arguments:
|
||||||
`nus3.py` can be modified along with the templates to specifiy offsets for other `.nus3bank` files
|
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.
|
||||||
|
BIN
TaikoNus3bankMake/bin/VGAudio.dll
Normal file
BIN
TaikoNus3bankMake/bin/VGAudio.dll
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/bin/VGAudioCli.exe
Normal file
BIN
TaikoNus3bankMake/bin/VGAudioCli.exe
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/bin/at9tool.exe
Normal file
BIN
TaikoNus3bankMake/bin/at9tool.exe
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/bin/encode.exe
Normal file
BIN
TaikoNus3bankMake/bin/encode.exe
Normal file
Binary file not shown.
70
TaikoNus3bankMake/conv.py
Normal file
70
TaikoNus3bankMake/conv.py
Normal 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()
|
@ -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)
|
|
44
TaikoNus3bankMake/script/at9/at9.py
Normal file
44
TaikoNus3bankMake/script/at9/at9.py
Normal 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}")
|
93
TaikoNus3bankMake/script/bnsf/bnsf.py
Normal file
93
TaikoNus3bankMake/script/bnsf/bnsf.py
Normal 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)
|
44
TaikoNus3bankMake/script/idsp/idsp.py
Normal file
44
TaikoNus3bankMake/script/idsp/idsp.py
Normal 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}")
|
45
TaikoNus3bankMake/script/lopus/lopus.py
Normal file
45
TaikoNus3bankMake/script/lopus/lopus.py
Normal 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}")
|
272
TaikoNus3bankMake/script/nus3/nus3.py
Normal file
272
TaikoNus3bankMake/script/nus3/nus3.py
Normal 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)
|
22
TaikoNus3bankMake/script/run.py
Normal file
22
TaikoNus3bankMake/script/run.py
Normal 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)
|
33
TaikoNus3bankMake/script/wav/wav.py
Normal file
33
TaikoNus3bankMake/script/wav/wav.py
Normal 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}")
|
BIN
TaikoNus3bankMake/templates/header.bnsf
Normal file
BIN
TaikoNus3bankMake/templates/header.bnsf
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/ns1/SONG_ABC.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/ns1/SONG_ABC.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/ns1/SONG_ABCD.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/ns1/SONG_ABCD.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/ns1/SONG_ABCDE.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/ns1/SONG_ABCDE.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/ns1/SONG_ABCDEF.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/ns1/SONG_ABCDEF.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/ps4/SONG_ABC.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/ps4/SONG_ABC.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/ps4/SONG_ABCD.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/ps4/SONG_ABCD.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/ps4/SONG_ABCDE.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/ps4/SONG_ABCDE.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/ps4/SONG_ABCDEF.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/ps4/SONG_ABCDEF.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/wiiu3/SONG_ABC.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/wiiu3/SONG_ABC.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/wiiu3/SONG_ABCD.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/wiiu3/SONG_ABCD.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/wiiu3/SONG_ABCDE.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/wiiu3/SONG_ABCDE.nus3bank
Normal file
Binary file not shown.
BIN
TaikoNus3bankMake/templates/wiiu3/SONG_ABCDEF.nus3bank
Normal file
BIN
TaikoNus3bankMake/templates/wiiu3/SONG_ABCDEF.nus3bank
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user