1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
"""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, INTERNAL
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.
"""
type = AUDIO
fade_out_mode = INTERNAL
def __init__(self, w, id_ = None, name = None, fade_in = 0, fade_out = 0,
filename: str = None):
super().__init__(w=w, id_=id_, name=name)
self._filename = filename
self._audio_id = self.id
if fade_in < 0 or fade_out < 0:
raise ValueError("Fades must be nonnegative")
self._fade_out = fade_out
self._fade_in = fade_in
if filename is not None:
self._actual_duration = self.w.get_audio_length(filename)
self._duration = max(0, self._actual_duration - self._fade_out)
self._audio_scope = frozenset(((filename,),))
else:
self._duration = 0
self._actual_duration = 0
self._audio_scope = frozenset()
@property
def fade_in(self):
return self._fade_in
@fade_in.setter
def fade_in(self, v):
if v < 0:
raise ValueError("Fades must be nonnegative")
if v != self._fade_in:
self._fade_in = v
self.w.function_changed(self)
@property
def fade_out(self):
return self._fade_out
@fade_out.setter
def fade_out(self, v):
if v < 0:
raise ValueError("Fades must be nonnegative")
if v != self._fade_out:
self._fade_out = v
self.w.function_changed(self)
@property
def scope(self):
return ()
@property
def audio_scope(self):
return self._audio_scope.union()
@property
def actual_duration(self):
return self._actual_duration
@property
def duration(self):
return self._duration
def _set_duration(self, value):
"""Set the duration.
This is called by Workspace when the duration for the file has been rechecked; do
not call this elsewhere, duration is intended to be immutable from outside the
library.
"""
if value != self._duration:
self._duration = max(value-self._fade_out, 0)
self._actual_duration = value
self.w.function_changed(self)
def get_data(self):
return None
def copy_data(self, data):
return None
def render(self, t, data = None):
return ((),
((self._audio_id, self._filename, 0, self.fade_in, max(0, self.duration-self.fade_out), 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._audio_id = hash((self._audio_id, self._filename))
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)
|