diff --git a/tests/test_adm.py b/tests/test_adm.py index d8de279..95c7bb5 100644 --- a/tests/test_adm.py +++ b/tests/test_adm.py @@ -23,6 +23,17 @@ class TestADMWave(TestCase): dict = adm.to_dict() self.assertIsNotNone(dict) + def test_programme(self): + info = wavinfo.WavInfoReader(self.protools_adm_wav) + adm = info.adm + pdict = adm.programme() + self.assertIn("programme_id", pdict.keys()) + self.assertIn("programme_name", pdict.keys()) + self.assertEqual(pdict['programme_id'], 'APR_1001') + self.assertEqual(pdict['programme_name'], 'Atmos_Master') + self.assertIn("contents", pdict.keys()) + self.assertEqual(len(pdict["contents"]), 3) + def test_track_info(self): info = wavinfo.WavInfoReader(self.protools_adm_wav) adm = info.adm diff --git a/wavinfo/__main__.py b/wavinfo/__main__.py index e34d19c..c3e585d 100644 --- a/wavinfo/__main__.py +++ b/wavinfo/__main__.py @@ -12,29 +12,53 @@ class MyJSONEncoder(json.JSONEncoder): else: return super().default(o) +class MissingDataError(RuntimeError): + pass + def main(): parser = OptionParser() - parser.usage = 'wavinfo [FILE.wav]*' + parser.usage = 'wavinfo [FILES]' # parser.add_option('-f', dest='output_format', help='Set the output format', # default='json', # metavar='FORMAT') + parser.add_option('--adm', dest='adm', help='Output ADM XML', + default=False, action='store_true') + + parser.add_option('--ixml', dest='ixml', help='Output iXML', + default=False, action='store_true') + (options, args) = parser.parse_args(sys.argv) for arg in args[1:]: try: this_file = WavInfoReader(path=arg) - ret_dict = {'file_argument': arg, 'run_date': datetime.datetime.now().isoformat() , 'scopes': {}} - for scope, name, value in this_file.walk(): - if scope not in ret_dict['scopes'].keys(): - ret_dict['scopes'][scope] = {} + if options.adm: + if this_file.adm: + sys.stdout.write(this_file.adm.xml_str()) + else: + raise MissingDataError("--adm option active but ADM metadata not present") + elif options.ixml: + if this_file.ixml: + sys.stdout.write(this_file.ixml.xml_bytes()) + else: + raise MissingDataError("--ixml option active but iXML metadata not present") + else: + ret_dict = {'file_argument': arg, 'run_date': datetime.datetime.now().isoformat() , 'scopes': {}} + for scope, name, value in this_file.walk(): + if scope not in ret_dict['scopes'].keys(): + ret_dict['scopes'][scope] = {} - ret_dict['scopes'][scope][name] = value + ret_dict['scopes'][scope][name] = value - json.dump(ret_dict, cls=MyJSONEncoder, fp=sys.stdout, indent=2) + json.dump(ret_dict, cls=MyJSONEncoder, fp=sys.stdout, indent=2) + except MissingDataError as e: + print("Missing metadata error in file %s" % arg, file=sys.stderr) + print(e, file=sys.stderr) + continue except Exception as e: - print(e) + raise e if __name__ == "__main__": diff --git a/wavinfo/wave_adm_reader.py b/wavinfo/wave_adm_reader.py index 79f33d7..9ee6bb3 100644 --- a/wavinfo/wave_adm_reader.py +++ b/wavinfo/wave_adm_reader.py @@ -41,6 +41,53 @@ class WavADMReader: offset += calcsize(uid_fmt) + def xml_str(self) -> str: + """ADM XML as a string""" + return ET.tostring(self.axml).decode("utf-8") + + def programme(self) -> dict: + """ + Extract the ADM audioProgramme data structure and some of its reference properties + """ + ret_dict = dict() + + nsmap = self.axml.getroot().nsmap + + afext = self.axml.find(".//audioFormatExtended", namespaces=nsmap) + + program = afext.find("audioProgramme", namespaces=nsmap) + ret_dict['programme_id'] = program.get("audioProgrammeID") + ret_dict['programme_name'] = program.get("audioProgrammeName") + ret_dict['programme_start'] = program.get("start") + ret_dict['programme_end'] = program.get("end") + ret_dict['contents'] = [] + + for content_ref in program.findall("audioContentIDRef", namespaces=nsmap): + content_dict = dict() + content_dict['content_id'] = cid = content_ref.text + content = afext.find("audioContent[@audioContentID='%s']" % cid, namespaces=nsmap) + content_dict['content_name'] = content.get("audioContentName") + content_dict['objects'] = [] + + for object_ref in content.findall("audioObjectIDRef", namespaces=nsmap): + object_dict = dict() + object_dict['object_id'] = oid = object_ref.text + object = afext.find("audioObject[@audioObjectID='%s']" % oid, namespaces=nsmap) + pack = object.find("audioPackFormatIDRef", namespaces=nsmap) + object_dict['object_name'] = object.get("audioObjectName") + object_dict['object_start'] = object.get("start") + object_dict['object_duration'] = object.get("duration") + object_dict['pack_id'] = pack.text + track_uid_list = [] + for t in object.findall("audioTrackUIDRef", namespaces=nsmap): + track_uid_list.append(t.text) + + object_dict['track_uids'] = track_uid_list + content_dict['objects'].append(object_dict) + + ret_dict['contents'].append(content_dict) + + return ret_dict def track_info(self, index): """ @@ -61,36 +108,36 @@ class WavADMReader: afext = self.axml.find(".//audioFormatExtended", namespaces=nsmap) - trackformat_elem = afext.find(".//audioTrackFormat[@audioTrackFormatID='%s']" % channel_info.track_ref, + trackformat_elem = afext.find("audioTrackFormat[@audioTrackFormatID='%s']" % channel_info.track_ref, namespaces=nsmap) stream_id = trackformat_elem[0].text - channelformatref_elem = afext.find(".//audioStreamFormat[@audioStreamFormatID='%s']/audioChannelFormatIDRef" % stream_id, + channelformatref_elem = afext.find("audioStreamFormat[@audioStreamFormatID='%s']/audioChannelFormatIDRef" % stream_id, namespaces=nsmap) channelformat_id = channelformatref_elem.text - packformatref_elem = afext.find(".//audioStreamFormat[@audioStreamFormatID='%s']/audioPackFormatIDRef" % stream_id, + packformatref_elem = afext.find("audioStreamFormat[@audioStreamFormatID='%s']/audioPackFormatIDRef" % stream_id, namespaces=nsmap) packformat_id = packformatref_elem.text - channelformat_elem = afext.find(".//audioChannelFormat[@audioChannelFormatID='%s']" % channelformat_id, + channelformat_elem = afext.find("audioChannelFormat[@audioChannelFormatID='%s']" % channelformat_id, namespaces=nsmap) ret_dict['channel_format_name'] = channelformat_elem.get("audioChannelFormatName") - packformat_elem = afext.find(".//audioPackFormat[@audioPackFormatID='%s']" % packformat_id, + packformat_elem = afext.find("audioPackFormat[@audioPackFormatID='%s']" % packformat_id, namespaces=nsmap) ret_dict['pack_type'] = packformat_elem.get("typeDefinition") ret_dict['pack_format_name'] = packformat_elem.get("audioPackFormatName") - object_elem = afext.find(".//audioObject[audioPackFormatIDRef = '%s']" % packformat_id, + object_elem = afext.find("audioObject[audioPackFormatIDRef = '%s']" % packformat_id, namespaces=nsmap) ret_dict['audio_object_name'] = object_elem.get("audioObjectName") object_id = object_elem.get("audioObjectID") ret_dict['object_id'] = object_id - content_elem = afext.find(".//audioContent/[audioObjectIDRef = '%s']" % object_id, + content_elem = afext.find("audioContent/[audioObjectIDRef = '%s']" % object_id, namespaces=nsmap) ret_dict['content_name'] = content_elem.get("audioContentName") @@ -108,4 +155,5 @@ class WavADMReader: rd.update(self.track_info(channel_uid_rec.track_index - 1)) return rd - return dict(channel_entries=list(map(lambda z: make_entry(z), self.channel_uids))) \ No newline at end of file + return dict(channel_entries=list(map(lambda z: make_entry(z), self.channel_uids)), + programme=self.programme()) \ No newline at end of file diff --git a/wavinfo/wave_ixml_reader.py b/wavinfo/wave_ixml_reader.py index 7e2e431..d014db0 100644 --- a/wavinfo/wave_ixml_reader.py +++ b/wavinfo/wave_ixml_reader.py @@ -27,6 +27,9 @@ class WavIXMLFormat: else: return None + def xml_bytes(self): + return ET.tostring(self.parsed).decode("utf-8") + @property def raw_xml(self): """