You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/AudioEngineWwise/Tools/WwiseAuthoringScripts/bank_info_parser.py

210 lines
8.2 KiB
Python

"""
Copyright (c) Contributors to the Open 3D Engine Project
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
from argparse import ArgumentParser
import glob
import json
import os
import sys
from xml.etree import ElementTree
__version__ = '0.1.0'
__copyright__ = 'Copyright (c) Contributors to the Open 3D Engine Project.'
metadata_file_extension = '.bankdeps'
metadata_version = '1.0'
init_bank_path = 'Init.bnk'
no_info_xml_error = 'SoundBanksInfo.xml does not exist, and there is more than ' \
'one bank (aside from Init.bnk), so complete dependency info of banks cannot be ' \
'determined. Please ensure "Project > Project Settings > SoundBanks > Generate ' \
'Metadata File" is enabled in your Wwise project to generate complete ' \
'dependencies. Limited dependency info will be generated.'
no_init_bank_error = 'There is no Init.bnk that exists at path {}. Init.bnk is ' \
'necessary for other soundbanks to work properly. Please regenerate soundbanks ' \
'from the Wwise project.'
class Bank:
def __init__(self, name):
self.path = name
self.embedded_media = [] # set of short ids for embedded media
self.streamed_media = [] # set of short ids for media being streamed
self.excluded_media = [] # set of short ids for media that is embedded in other banks
self.included_events = [] # set of names of events that are included in the bank.
self.metadata_object = {} # object that will be serialized to JSON for this bank
self.metadata_path = "" # path to serialize the metadata object to
def parse_args():
parser = ArgumentParser(description='Generate metadata files for all banks referenced in a SoundBankInfo.xml')
parser.add_argument('soundbank_info_path', help='Path to a SoundBankInfo.xml to parse')
parser.add_argument('output_path', help='Path that the banks have been output to, where these metadata files will live as well.')
options = parser.parse_args()
if not os.path.exists(options.output_path):
sys.exit('Output path {} does not exist'.format(options.output_path))
return options
def parse_bank_info_xml(root):
sound_banks_element = root.find('SoundBanks')
banks = []
for bank_element in sound_banks_element.iter('SoundBank'):
bank_path = bank_element.find('Path').text
# If this is the init bank, then skip as it doesn't need an entry, as the init bank does not need metadata
if bank_path == init_bank_path:
continue
bank = Bank(bank_path)
for embedded_file in bank_element.findall('IncludedMemoryFiles/File'):
bank.embedded_media.append(embedded_file.get('Id'))
for streamed_file in bank_element.findall('ReferencedStreamedFiles/File'):
bank.streamed_media.append(streamed_file.get('Id'))
for excluded_file in bank_element.findall('ExcludedMemoryFiles/File'):
bank.excluded_media.append(excluded_file.get('Id'))
for event_name in bank_element.findall('IncludedEvents/Event'):
bank.included_events.append(event_name.get('Name'))
for embedded_file in event_name.findall('IncludedMemoryFiles/File'):
bank.embedded_media.append(embedded_file.get('Id'))
for streamed_file in event_name.findall('ReferencedStreamedFiles/File'):
bank.streamed_media.append(streamed_file.get('Id'))
for excluded_file in event_name.findall('ExcludedMemoryFiles/File'):
bank.excluded_media.append(excluded_file.get('Id'))
banks.append(bank)
return banks
def make_banks_from_file_paths(bank_paths):
return [Bank(bank) for bank in bank_paths]
def build_media_to_bank_dictionary(banks):
media_to_banks = {}
for bank in banks:
for short_id in bank.embedded_media:
media_to_banks[short_id] = bank
return media_to_banks
def serialize_metadata_list(banks):
for bank in banks:
# generate metadata json file
with open(bank.metadata_path, 'w') as metadata_file:
json.dump(bank.metadata_object, metadata_file, indent=4)
def generate_default_metadata_path_and_object(bank_path, output_path):
metadata_file_path = os.path.join(output_path, bank_path)
metadata_file_path = os.path.splitext(metadata_file_path)[0] + metadata_file_extension
metadata = dict()
metadata['version'] = metadata_version
metadata['bankName'] = bank_path
return metadata_file_path, metadata
def generate_bank_metadata(banks, media_dictionary, output_path):
for bank in banks:
# Determine path for metadata file.
metadata_file_path, metadata = generate_default_metadata_path_and_object(bank.path, output_path)
# Determine paths for each of the streamed files.
dependencies = set()
for short_id in bank.streamed_media:
dependencies.add(str.format("{}.wem", short_id))
# Any media that has been excluded from this bank and embedded in another, add that bank as a dependency
for short_id in bank.excluded_media:
dependencies.add(media_dictionary[short_id].path)
# Force a dependency on the init bank.
dependencies.add(init_bank_path)
metadata['dependencies'] = list(dependencies)
metadata['includedEvents'] = bank.included_events
# Push the data generated bank into the bank to be used later (by tests or by serializer).
bank.metadata_object = metadata
bank.metadata_path = metadata_file_path
return banks
def register_wems_as_streamed_file_dependencies(bank, output_path):
for wem_file in glob.glob1(output_path, '*.wem'):
bank.streamed_media.append(os.path.splitext(wem_file)[0])
def generate_metadata(soundbank_info_path, output_path):
bank_paths = glob.glob1(output_path, '*.bnk')
soundbank_xml_exists = os.path.exists(soundbank_info_path)
error_code = 0
banks_with_metadata = dict()
init_bank_exists = init_bank_path in bank_paths
if init_bank_exists:
bank_paths.remove(init_bank_path)
else:
print(str.format(no_init_bank_error, output_path))
error_code = max(error_code, 1)
# Check to see if the soundbankinfo file exists. If it doesn't then there are no streamed files.
if soundbank_xml_exists:
root = ElementTree.parse(soundbank_info_path).getroot()
banks = parse_bank_info_xml(root)
media_dictionary = build_media_to_bank_dictionary(banks)
banks_with_metadata = generate_bank_metadata(banks, media_dictionary, output_path)
# If there are more than two content banks in the directory, then there is
# no way to generate dependencies properly without the XML.
# Just generate the dependency on the init bank and generate a warning.
elif len(bank_paths) > 1:
print(no_info_xml_error)
error_code = max(error_code, 1)
banks = make_banks_from_file_paths(bank_paths)
media_dictionary = build_media_to_bank_dictionary(banks)
banks_with_metadata = generate_bank_metadata(banks, media_dictionary, output_path)
# There is one content bank, so this bank depends on the init bank and all wem files in the output path
elif len(bank_paths) == 1:
banks = make_banks_from_file_paths(bank_paths)
# populate bank streamed file dependencies with all the wems in the folder.
register_wems_as_streamed_file_dependencies(banks[0], output_path)
media_dictionary = build_media_to_bank_dictionary(banks)
banks_with_metadata = generate_bank_metadata(banks, media_dictionary, output_path)
# There were no banks in the directory, and no metadata xml, then we can't generate any dependencies
elif not init_bank_exists:
print(str.format(no_init_bank_error, output_path))
error_code = max(error_code, 2)
return banks_with_metadata, error_code
def main():
print('Wwise Bank Info Parser v{}'.format(__version__))
print(__copyright__)
print()
args = parse_args()
banks, error_code = generate_metadata(args.soundbank_info_path, args.output_path)
if banks is not None:
serialize_metadata_list(banks)
return error_code
if __name__ == '__main__':
main()