""" All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or its licensors. For complete copyright and license terms please see the LICENSE at the root of this distribution (the "License"). All use of this software is governed by the License, or, if provided, by the license below or the license accompanying this file. Do not remove or modify any license notices. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. """ from argparse import ArgumentParser import glob import json import os import sys from xml.etree import ElementTree __version__ = '0.1.0' __copyright__ = 'Copyright (c) Amazon.com, Inc.' 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')) 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) is 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()