bank_info_parser.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. """
  6. from argparse import ArgumentParser
  7. import glob
  8. import json
  9. import os
  10. import sys
  11. from xml.etree import ElementTree
  12. __version__ = '0.1.0'
  13. __copyright__ = 'Copyright (c) Contributors to the Open 3D Engine Project.\nFor complete copyright and license terms please see the LICENSE at the root of this distribution.'
  14. metadata_file_extension = '.bankdeps'
  15. metadata_version = '1.0'
  16. init_bank_path = 'Init.bnk'
  17. no_info_xml_error = 'SoundBanksInfo.xml does not exist, and there is more than ' \
  18. 'one bank (aside from Init.bnk), so complete dependency info of banks cannot be ' \
  19. 'determined. Please ensure "Project > Project Settings > SoundBanks > Generate ' \
  20. 'Metadata File" is enabled in your Wwise project to generate complete ' \
  21. 'dependencies. Limited dependency info will be generated.'
  22. no_init_bank_error = 'There is no Init.bnk that exists at path {}. Init.bnk is ' \
  23. 'necessary for other soundbanks to work properly. Please regenerate soundbanks ' \
  24. 'from the Wwise project.'
  25. class Bank:
  26. def __init__(self, name):
  27. self.path = name
  28. self.embedded_media = [] # set of short ids for embedded media
  29. self.streamed_media = [] # set of short ids for media being streamed
  30. self.excluded_media = [] # set of short ids for media that is embedded in other banks
  31. self.included_events = [] # set of names of events that are included in the bank.
  32. self.metadata_object = {} # object that will be serialized to JSON for this bank
  33. self.metadata_path = "" # path to serialize the metadata object to
  34. def parse_args():
  35. parser = ArgumentParser(description='Generate metadata files for all banks referenced in a SoundBankInfo.xml')
  36. parser.add_argument('soundbank_info_path', help='Path to a SoundBankInfo.xml to parse')
  37. parser.add_argument('output_path', help='Path that the banks have been output to, where these metadata files will live as well.')
  38. options = parser.parse_args()
  39. if not os.path.exists(options.output_path):
  40. sys.exit('Output path {} does not exist'.format(options.output_path))
  41. return options
  42. def parse_bank_info_xml(root):
  43. sound_banks_element = root.find('SoundBanks')
  44. banks = []
  45. for bank_element in sound_banks_element.iter('SoundBank'):
  46. bank_path = bank_element.find('Path').text
  47. # If this is the init bank, then skip as it doesn't need an entry, as the init bank does not need metadata
  48. if bank_path == init_bank_path:
  49. continue
  50. bank = Bank(bank_path)
  51. for embedded_file in bank_element.findall('IncludedMemoryFiles/File'):
  52. bank.embedded_media.append(embedded_file.get('Id'))
  53. for streamed_file in bank_element.findall('ReferencedStreamedFiles/File'):
  54. bank.streamed_media.append(streamed_file.get('Id'))
  55. for excluded_file in bank_element.findall('ExcludedMemoryFiles/File'):
  56. bank.excluded_media.append(excluded_file.get('Id'))
  57. for event_name in bank_element.findall('IncludedEvents/Event'):
  58. bank.included_events.append(event_name.get('Name'))
  59. for embedded_file in event_name.findall('IncludedMemoryFiles/File'):
  60. bank.embedded_media.append(embedded_file.get('Id'))
  61. for streamed_file in event_name.findall('ReferencedStreamedFiles/File'):
  62. bank.streamed_media.append(streamed_file.get('Id'))
  63. for excluded_file in event_name.findall('ExcludedMemoryFiles/File'):
  64. bank.excluded_media.append(excluded_file.get('Id'))
  65. banks.append(bank)
  66. return banks
  67. def make_banks_from_file_paths(bank_paths):
  68. return [Bank(bank) for bank in bank_paths]
  69. def build_media_to_bank_dictionary(banks):
  70. media_to_banks = {}
  71. for bank in banks:
  72. for short_id in bank.embedded_media:
  73. media_to_banks[short_id] = bank
  74. return media_to_banks
  75. def serialize_metadata_list(banks):
  76. for bank in banks:
  77. # generate metadata json file
  78. with open(bank.metadata_path, 'w') as metadata_file:
  79. json.dump(bank.metadata_object, metadata_file, indent=4)
  80. def generate_default_metadata_path_and_object(bank_path, output_path):
  81. metadata_file_path = os.path.join(output_path, bank_path)
  82. metadata_file_path = os.path.splitext(metadata_file_path)[0] + metadata_file_extension
  83. metadata = dict()
  84. metadata['version'] = metadata_version
  85. metadata['bankName'] = bank_path
  86. return metadata_file_path, metadata
  87. def generate_bank_metadata(banks, media_dictionary, output_path):
  88. for bank in banks:
  89. # Determine path for metadata file.
  90. metadata_file_path, metadata = generate_default_metadata_path_and_object(bank.path, output_path)
  91. # Determine paths for each of the streamed files.
  92. dependencies = set()
  93. for short_id in bank.streamed_media:
  94. dependencies.add(str.format("{}.wem", short_id))
  95. # Any media that has been excluded from this bank and embedded in another, add that bank as a dependency
  96. for short_id in bank.excluded_media:
  97. dependencies.add(media_dictionary[short_id].path)
  98. # Force a dependency on the init bank.
  99. dependencies.add(init_bank_path)
  100. metadata['dependencies'] = list(dependencies)
  101. metadata['includedEvents'] = bank.included_events
  102. # Push the data generated bank into the bank to be used later (by tests or by serializer).
  103. bank.metadata_object = metadata
  104. bank.metadata_path = metadata_file_path
  105. return banks
  106. def register_wems_as_streamed_file_dependencies(bank, output_path):
  107. for wem_file in glob.glob1(output_path, '*.wem'):
  108. bank.streamed_media.append(os.path.splitext(wem_file)[0])
  109. def generate_metadata(soundbank_info_path, output_path):
  110. bank_paths = glob.glob1(output_path, '*.bnk')
  111. soundbank_xml_exists = os.path.exists(soundbank_info_path)
  112. error_code = 0
  113. banks_with_metadata = dict()
  114. init_bank_exists = init_bank_path in bank_paths
  115. if init_bank_exists:
  116. bank_paths.remove(init_bank_path)
  117. else:
  118. print(str.format(no_init_bank_error, output_path))
  119. error_code = max(error_code, 1)
  120. # Check to see if the soundbankinfo file exists. If it doesn't then there are no streamed files.
  121. if soundbank_xml_exists:
  122. root = ElementTree.parse(soundbank_info_path).getroot()
  123. banks = parse_bank_info_xml(root)
  124. media_dictionary = build_media_to_bank_dictionary(banks)
  125. banks_with_metadata = generate_bank_metadata(banks, media_dictionary, output_path)
  126. # If there are more than two content banks in the directory, then there is
  127. # no way to generate dependencies properly without the XML.
  128. # Just generate the dependency on the init bank and generate a warning.
  129. elif len(bank_paths) > 1:
  130. print(no_info_xml_error)
  131. error_code = max(error_code, 1)
  132. banks = make_banks_from_file_paths(bank_paths)
  133. media_dictionary = build_media_to_bank_dictionary(banks)
  134. banks_with_metadata = generate_bank_metadata(banks, media_dictionary, output_path)
  135. # There is one content bank, so this bank depends on the init bank and all wem files in the output path
  136. elif len(bank_paths) == 1:
  137. banks = make_banks_from_file_paths(bank_paths)
  138. # populate bank streamed file dependencies with all the wems in the folder.
  139. register_wems_as_streamed_file_dependencies(banks[0], output_path)
  140. media_dictionary = build_media_to_bank_dictionary(banks)
  141. banks_with_metadata = generate_bank_metadata(banks, media_dictionary, output_path)
  142. # There were no banks in the directory, and no metadata xml, then we can't generate any dependencies
  143. elif not init_bank_exists:
  144. print(str.format(no_init_bank_error, output_path))
  145. error_code = max(error_code, 2)
  146. return banks_with_metadata, error_code
  147. def main():
  148. print('Wwise Bank Info Parser v{}'.format(__version__))
  149. print(__copyright__)
  150. print()
  151. args = parse_args()
  152. banks, error_code = generate_metadata(args.soundbank_info_path, args.output_path)
  153. if banks is not None:
  154. serialize_metadata_list(banks)
  155. return error_code
  156. if __name__ == '__main__':
  157. main()