summaryrefslogtreecommitdiff
path: root/functions/audio.py
diff options
context:
space:
mode:
Diffstat (limited to 'functions/audio.py')
-rw-r--r--functions/audio.py118
1 files changed, 118 insertions, 0 deletions
diff --git a/functions/audio.py b/functions/audio.py
new file mode 100644
index 0000000..d0e599d
--- /dev/null
+++ b/functions/audio.py
@@ -0,0 +1,118 @@
+"""Audio function module.
+
+Contains the definition of the Audio, the audio primitive.
+"""
+
+import xml.etree.ElementTree as et
+
+from .function import Function
+
+from ..constants import AUDIO, BXW
+from ..exceptions import LoadError
+
+class Audio(Function):
+ """Class representing an audio cue.
+
+ This is the primitive for audio, and all sound cues must be based in some manner off of
+ Audio. This function merely plays a single audio file once, starting at t=0.
+
+ The duration of the audio is automatically determined and is zero if the file does not
+ exist or is unsupported by ffmpeg.
+ """
+ scope = ()
+ audio_scope = frozenset()
+ actual_duration = 0
+
+ def __init__(self, w, id_ = None, name = None, fade_in = 0, fade_out = 0,
+ filename: str = None):
+ super().__init__(w=w, type_=AUDIO, id_=id_, name=name, duration=0,
+ fade_in=fade_in, fade_out=fade_out)
+
+ self._filename = filename
+
+ if filename is not None:
+ self.duration = self.w.get_audio_length(filename)
+ self.audio_scope = frozenset(((filename,),))
+ else:
+ self.duration = 0
+ self.audio_scope = frozenset()
+
+ self.actual_duration = self.duration
+
+ def get_data(self):
+ return None
+
+ def copy_data(self, data):
+ return None
+
+ def render(self, t, data = None):
+ return ((),
+ ((self.id, self._filename, 0, self.fade_in, self.duration, self.fade_out),),
+ None)
+
+ @property
+ def filename(self):
+ """Return the current audio filename."""
+ return self._filename
+
+ @filename.setter
+ def filename(self, value: str):
+ """Set the current audio filename, updating the duration."""
+ if value is not None:
+ self.duration = self.w.get_audio_length(value)
+ self.audio_scope = frozenset(((value,),))
+ else:
+ self.duration = 0
+ self.audio_scope = frozenset()
+
+ self._filename = value
+ self.w.function_changed(self)
+
+ def serialize(self) -> et.Element:
+ e = et.Element(BXW+"function")
+ e.set("type", self.type)
+ e.set("id", str(self.id))
+ e.set("name", self.name)
+ e.set("fade-in", str(self.fade_in))
+ e.set("fade-out", str(self.fade_out))
+ if self.filename is not None:
+ filename = et.SubElement(e, BXW+"filename")
+ filename.text = self.filename
+
+ return e
+
+ @classmethod
+ def deserialize(cls, w, e):
+ if e.tag != BXW+"function":
+ raise LoadError("Invalid function tag")
+ elif e.get("type") != AUDIO:
+ raise LoadError("Load delegated to wrong class (this is a bug)")
+
+ id_ = cls.int_or_none(e.get("id"))
+ if id_ is None:
+ raise LoadError("Function tag has invalid/missing ID")
+
+ name = e.get("name")
+
+ fade_in = e.get("fade-in")
+ try:
+ fade_in = int(fade_in) if fade_in else 0
+ except ValueError:
+ raise LoadError("Invalid fade in")
+
+ fade_out = e.get("fade-out")
+ try:
+ fade_out = int(fade_out) if fade_out else 0
+ except ValueError:
+ raise LoadError("Invalid fade out")
+
+ if len(e) > 1:
+ raise LoadError("Audio tag can have at most one filename")
+ elif len(e) == 1:
+ filename, = e
+ filename = filename.text
+ else:
+ filename = None
+
+ return cls(w=w, id_=id_, name=name, filename=filename, fade_in=fade_in,
+ fade_out=fade_out)