diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..823f5e2d Binary files /dev/null and b/.DS_Store differ diff --git a/ear/.DS_Store b/ear/.DS_Store new file mode 100644 index 00000000..2a5e7d2f Binary files /dev/null and b/ear/.DS_Store differ diff --git a/ear/core/.DS_Store b/ear/core/.DS_Store new file mode 100644 index 00000000..10b2579f Binary files /dev/null and b/ear/core/.DS_Store differ diff --git a/ear/fileio/.DS_Store b/ear/fileio/.DS_Store new file mode 100644 index 00000000..d84f8d58 Binary files /dev/null and b/ear/fileio/.DS_Store differ diff --git a/ear/fileio/adm/.DS_Store b/ear/fileio/adm/.DS_Store new file mode 100644 index 00000000..2fdb0a8a Binary files /dev/null and b/ear/fileio/adm/.DS_Store differ diff --git a/ear/fileio/adm/elements/__init__.py b/ear/fileio/adm/elements/__init__.py index 504e0794..91213ae0 100644 --- a/ear/fileio/adm/elements/__init__.py +++ b/ear/fileio/adm/elements/__init__.py @@ -1,10 +1,12 @@ # flake8: noqa from .main_elements import (AudioChannelFormat, AudioPackFormat, AudioTrackFormat, - AudioStreamFormat, AudioProgramme, AudioContent, AudioObject, AudioTrackUID, - FormatDefinition, TypeDefinition, Frequency) + AudioStreamFormat, AudioProgramme, AudioContent, + AudioObject, AudioTrackUID, FormatDefinition, TypeDefinition, + Frequency) from .block_formats import (AudioBlockFormatObjects, ChannelLock, ObjectDivergence, - JumpPosition, AudioBlockFormatDirectSpeakers, AudioBlockFormatBinaural, AudioBlockFormatHoa, - AudioBlockFormatMatrix, MatrixCoefficient, + JumpPosition, AudioBlockFormatDirectSpeakers, AudioBlockFormatBinaural, + AudioBlockFormatHoa, AudioBlockFormatMatrix, MatrixCoefficient, CartesianZone, PolarZone) from .geom import (DirectSpeakerPolarPosition, DirectSpeakerCartesianPosition, BoundCoordinate, ObjectPolarPosition, ObjectCartesianPosition, ScreenEdgeLock) +from .interaction import (AudioObjectInteraction, GainInteractionRange, PositionInteractionRange) diff --git a/ear/fileio/adm/elements/interaction.py b/ear/fileio/adm/elements/interaction.py new file mode 100644 index 00000000..b18d9973 --- /dev/null +++ b/ear/fileio/adm/elements/interaction.py @@ -0,0 +1,37 @@ +from attr import attrs, attrib, Factory +from attr.validators import instance_of, optional + + +@attrs(slots=True) +class PositionInteractionRange(object): + minAzimuth = attrib(default=None, validator=optional(instance_of(float))) + maxAzimuth = attrib(default=None, validator=optional(instance_of(float))) + minElevation = attrib(default=None, validator=optional(instance_of(float))) + maxElevation = attrib(default=None, validator=optional(instance_of(float))) + minDistance = attrib(default=None, validator=optional(instance_of(float))) + maxDistance = attrib(default=None, validator=optional(instance_of(float))) + minX = attrib(default=None, validator=optional(instance_of(float))) + maxX = attrib(default=None, validator=optional(instance_of(float))) + minY = attrib(default=None, validator=optional(instance_of(float))) + maxY = attrib(default=None, validator=optional(instance_of(float))) + minZ = attrib(default=None, validator=optional(instance_of(float))) + maxZ = attrib(default=None, validator=optional(instance_of(float))) + + +@attrs(slots=True) +class GainInteractionRange(object): + min = attrib(default=None, validator=optional(instance_of(float))) + max = attrib(default=None, validator=optional(instance_of(float))) + + +@attrs(slots=True) +class AudioObjectInteraction(object): + onOffInteract = attrib(validator=instance_of(bool)) + gainInteract = attrib(default=None, validator=optional(instance_of(bool))) + positionInteract = attrib(default=None, validator=optional(instance_of(bool))) + gainInteractionRange = attrib(default=None, + validator=optional(instance_of(GainInteractionRange)), + ) + positionInteractionRange = attrib(default=None, + validator=optional(instance_of(PositionInteractionRange)), + ) diff --git a/ear/fileio/adm/elements/main_elements.py b/ear/fileio/adm/elements/main_elements.py index 58c4e65d..acc29a1f 100644 --- a/ear/fileio/adm/elements/main_elements.py +++ b/ear/fileio/adm/elements/main_elements.py @@ -6,6 +6,7 @@ from ..exceptions import AdmError from ....common import CartesianScreen, PolarScreen, default_screen, list_of +from .interaction import AudioObjectInteraction def _lookup_elements(adm, idRefs): @@ -83,7 +84,6 @@ def lazy_lookup_references(self, adm): self.audioObjects = _lookup_elements(adm, self.audioObjectIDRef) self.audioObjectIDRef = None - @attrs(slots=True) class AudioObject(ADMElement): audioObjectName = attrib(default=None, validator=instance_of(string_types)) @@ -97,6 +97,8 @@ class AudioObject(ADMElement): audioTrackUIDs = attrib(default=Factory(list), repr=False) audioObjects = attrib(default=Factory(list), repr=False) audioComplementaryObjects = attrib(default=Factory(list), repr=False) + audioObjectInteraction = attrib(default=None, + validator=optional(instance_of(AudioObjectInteraction))) audioPackFormatIDRef = attrib(default=None) audioTrackUIDRef = attrib(default=None) diff --git a/ear/fileio/adm/test/.DS_Store b/ear/fileio/adm/test/.DS_Store new file mode 100644 index 00000000..cebb471e Binary files /dev/null and b/ear/fileio/adm/test/.DS_Store differ diff --git a/ear/fileio/adm/test/test_adm_files/base.xml b/ear/fileio/adm/test/test_adm_files/base.xml index 61b0329d..f2a58977 100644 --- a/ear/fileio/adm/test/test_adm_files/base.xml +++ b/ear/fileio/adm/test/test_adm_files/base.xml @@ -12,6 +12,14 @@ AP_00031001 ATU_00000001 + + 0.5 + 2.0 + -10.0 + +10.0 + -30.0 + +30.0 + AC_00031001 diff --git a/ear/fileio/adm/test/test_xml.py b/ear/fileio/adm/test/test_xml.py index d4109310..9198def1 100644 --- a/ear/fileio/adm/test/test_xml.py +++ b/ear/fileio/adm/test/test_xml.py @@ -111,6 +111,8 @@ def get_acf(adm): bf_path = "//adm:audioBlockFormat" +def test_interact(base): + assert base.adm_after_mods() def test_gain(base): assert base.bf_after_mods(add_children(bf_path, E.gain("0"))).gain == 0.0 @@ -719,6 +721,7 @@ def check_round_trip(adm): xml = adm_to_xml(adm) xml_str = lxml.etree.tostring(xml, pretty_print=True) parsed_adm = parse_string(xml_str) + print(xml_str) assert len(list(parsed_adm.elements)) == len(list(adm.elements)) diff --git a/ear/fileio/adm/xml.py b/ear/fileio/adm/xml.py index 2405a4bf..c56af471 100644 --- a/ear/fileio/adm/xml.py +++ b/ear/fileio/adm/xml.py @@ -13,14 +13,13 @@ AudioBlockFormatObjects, AudioBlockFormatDirectSpeakers, AudioBlockFormatBinaural, AudioBlockFormatHoa, AudioBlockFormatMatrix, ChannelLock, BoundCoordinate, JumpPosition, ObjectDivergence, CartesianZone, PolarZone, ScreenEdgeLock, MatrixCoefficient) from .elements import ( - AudioProgramme, AudioContent, AudioObject, AudioChannelFormat, AudioPackFormat, AudioStreamFormat, AudioTrackFormat, AudioTrackUID, - FormatDefinition, TypeDefinition, Frequency) + AudioProgramme, AudioContent, AudioObject, AudioObjectInteraction, AudioChannelFormat, AudioPackFormat, AudioStreamFormat, AudioTrackFormat, AudioTrackUID, + FormatDefinition, GainInteractionRange, PositionInteractionRange, TypeDefinition, Frequency) from .elements.geom import (DirectSpeakerPolarPosition, DirectSpeakerCartesianPosition, ObjectPolarPosition, ObjectCartesianPosition) from .time_format import parse_time, unparse_time from ...common import PolarPosition, CartesianPosition, CartesianScreen, PolarScreen - namespaces = [None, "urn:ebu:metadata-schema:ebuCore_2014", "urn:ebu:metadata-schema:ebuCore_2015", @@ -875,6 +874,65 @@ def frequency_to_xml(parent, obj): parent.append(element) +def handle_objectInteraction(kwargs, el): + objectInteraction = kwargs.setdefault("audioObjectInteraction", + AudioObjectInteraction(onOffInteract=BoolType.loads_func(el.attrib["onOffInteract"]))) + if objectInteraction.gainInteract is not None: + objectInteraction.gainInteract = BoolType.loads_func(el.attrib["gainInteract"]) + if objectInteraction.positionInteract is not None: + objectInteraction.positionInteract = BoolType.loads_func(el.attrib["positionInteract"]) + + if any([e.text for e in el.getiterator() if e.tag in qnames("gainInteractionRange")]): + objectInteraction.gainInteractionRange = GainInteractionRange() + if any([e.text for e in el.getiterator() if e.tag in qnames("positionInteractionRange")]): + objectInteraction.positionInteractionRange = PositionInteractionRange() + for element in el.getiterator(): + if element.tag in qnames("gainInteractionRange"): + if element.attrib['bound'] == 'min': + objectInteraction.gainInteractionRange.min = float(element.text) + elif element.attrib['bound'] == 'max': + objectInteraction.gainInteractionRange.max = float(element.text) + elif element.tag in qnames("positionInteractionRange"): + attribute = str(element.attrib['bound'])+str(element.attrib['coordinate']).capitalize() + if attribute in [a for a in dir(objectInteraction.positionInteractionRange) if not a.startswith('__')]: + setattr(objectInteraction.positionInteractionRange, attribute, float(element.text)) + +def objectInteraction_to_xml(parent, obj): + if obj.audioObjectInteraction is not None: + if obj.audioObjectInteraction.gainInteract is not None and obj.audioObjectInteraction.positionInteract is not None: + element = parent.makeelement(QName(default_ns, "audioObjectInteraction"), + onOffInteract=BoolType.dumps(obj.audioObjectInteraction.onOffInteract), + gainInteract=BoolType.dumps(obj.audioObjectInteraction.gainInteract), + positionInteract=BoolType.dumps(obj.audioObjectInteraction.positionInteract)) + elif obj.audioObjectInteraction.gainInteract is not None and obj.audioObjectInteraction.positionInteract is None: + element = parent.makeelement(QName(default_ns, "audioObjectInteraction"), + onOffInteract=BoolType.dumps(obj.audioObjectInteraction.onOffInteract), + gainInteract=BoolType.dumps(obj.audioObjectInteraction.gainInteract)) + elif obj.audioObjectInteraction.gainInteract is None and obj.audioObjectInteraction.positionInteract is not None: + element = parent.makeelement(QName(default_ns, "audioObjectInteraction"), + onOffInteract=BoolType.dumps(obj.audioObjectInteraction.onOffInteract), + positionInteract=BoolType.dumps(obj.audioObjectInteraction.positionInteract)) + else: + element = parent.makeelement(QName(default_ns, "audioObjectInteraction"), + onOffInteract=BoolType.dumps(obj.audioObjectInteraction.onOffInteract)) + + if obj.audioObjectInteraction.gainInteractionRange is not None: + for attribute in [a for a in dir(obj.audioObjectInteraction.gainInteractionRange) if not a.startswith('__')]: + if getattr(obj.audioObjectInteraction.gainInteractionRange, attribute) is not None: + element_c = element.makeelement(QName(default_ns, "gainInteractionRange"), + bound=attribute) + element_c.text = FloatType.dumps(getattr(obj.audioObjectInteraction.gainInteractionRange, attribute)) + element.append(element_c) + if obj.audioObjectInteraction.positionInteractionRange is not None: + for attribute in [a for a in dir(obj.audioObjectInteraction.positionInteractionRange) if not a.startswith('__')]: + if getattr(obj.audioObjectInteraction.positionInteractionRange, attribute) is not None: + element_c = element.makeelement(QName(default_ns, "positionInteractionRange"), + coordinate=attribute[3:].lower(), bound=attribute[:3]) + element_c.text = FloatType.dumps(getattr(obj.audioObjectInteraction.positionInteractionRange, attribute)) + element.append(element_c) + parent.append(element) + + channel_format_handler = ElementParser(AudioChannelFormat, "audioChannelFormat", [ Attribute(adm_name="audioChannelFormatID", arg_name="id", required=True), Attribute(adm_name="audioChannelFormatName", arg_name="audioChannelFormatName", required=True), @@ -1052,6 +1110,8 @@ def make_audio_programme(referenceScreen=None, **kwargs): RefList("audioObject"), ]) + + object_handler = ElementParser(AudioObject, "audioObject", [ Attribute(adm_name="audioObjectID", arg_name="id", required=True), Attribute(adm_name="audioObjectName", arg_name="audioObjectName", required=True), @@ -1065,6 +1125,7 @@ def make_audio_programme(referenceScreen=None, **kwargs): RefList("audioObject"), RefList("audioComplementaryObject"), ListElement(adm_name="audioTrackUIDRef", arg_name="audioTrackUIDRef", attr_name="audioTrackUIDs", type=TrackUIDRefType), + CustomElement("audioObjectInteraction", handle_objectInteraction, to_xml=objectInteraction_to_xml), ]) track_uid_handler = ElementParser(AudioTrackUID, "audioTrackUID", [