Codebase list voctomix / be45bea
Import upstream version 1.3+git20201127.1.3156f35 Debian Janitor 2 years ago
171 changed file(s) with 17679 addition(s) and 3908 deletion(s). Raw diff Collapse all Expand all
77 #
88 ## core:
99 # docker run -it --rm -v /some/dir:/video
10 # -p 9999:9999 -p 10000:10000 -p 10001:10001 -p 10002:10002 -p 11000:11000 -p 12000:12000 \
11 # -p 13000:13000 -p 13001:13001 -p 13002:13002 -p 14000:14000 -p 15000:15000 -p 16000:16000 \
12 # -p 17000:17000 -p 17001:17001 -p 17002:17002 -p 18000:18000 --name=voctocore local/voctomix core
10 # -p 9999:9999 -p 10000:10000 -p 10001:10001 -p 10002:10002 -p 11000:11000 -p 12000:12000 \
11 # -p 13000:13000 -p 13001:13001 -p 13002:13002 -p 13100:13100 -p 14000:14000 -p 15000:15000 -p 16000:16000 \
12 # -p 17000:17000 -p 17001:17001 -p 17002:17002 -p 18000:18000 --name=voctocore local/voctomix core
1313 #
1414 ## test sources
1515 # docker run -it --rm --name=cam1 --link=voctocore:corehost local/voctomix gstreamer/source-videotestsrc-as-cam1.sh
2222 # -v /tmp/vocto/configgui.ini:/opt/voctomix/voctogui/config.ini -v /tmp/.X11-unix:/tmp/.X11-unix -v /tmp/.docker.xauth:/tmp/.docker.xauth local/voctomix gui
2323
2424
25 FROM ubuntu:wily
25 FROM ubuntu:bionic
2626
2727 MAINTAINER Bjoern Riemer <bjoern.riemer@web.de>
2828
3434 RUN useradd -m voc
3535
3636 RUN apt-get update \
37 && apt-get install -y gstreamer1.0-plugins-good vim-tiny wget \
38 && apt-get install -y --no-install-recommends gstreamer1.0-tools libgstreamer1.0-0 python3 python3-gi gir1.2-gstreamer-1.0 gstreamer1.0-plugins-bad \
39 && apt-get install -y gir1.2-gst-plugins-base-1.0 gir1.2-gstreamer-1.0 gir1.2-gtk-3.0 gstreamer1.0-x ffmpeg python3-gi-cairo \
40 && apt-get clean
37 && apt-get install -y \
38 gstreamer1.0-plugins-good \
39 wget \
40 && apt-get install -y --no-install-recommends \
41 gstreamer1.0-tools \
42 libgstreamer1.0-0 \
43 python3 \
44 python3-gi \
45 gir1.2-gstreamer-1.0 \
46 gstreamer1.0-plugins-bad \
47 && apt-get install -y \
48 gir1.2-gst-plugins-base-1.0 \
49 gir1.2-gstreamer-1.0 \
50 gir1.2-gtk-3.0 \
51 gstreamer1.0-x \
52 ffmpeg \
53 python3-gi-cairo \
54 python3-sdnotify \
55 python3-scipy \
56 && apt-get clean
4157
4258 RUN wget -q https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64 -O /bin/gosu && chmod +x /bin/gosu
4359
4460 RUN mkdir -p /opt/voctomix
4561
46 EXPOSE 9998 9999 10000 10001 10002 11000 12000 13000 13001 13002 14000 15000 16000 17000 17001 17002 18000
62 EXPOSE 9998 9999 10000 10001 10002 11000 12000 13000 13001 13002 13100 14000 15000 16000 17000 17001 17002 18000
4763 VOLUME /video
4864
4965 WORKDIR /opt/voctomix
5066 COPY . /opt/voctomix/
51 COPY docker-ep.sh /opt/voctomix/
67 COPY docker-entrypoint.sh /opt/voctomix/
5268
5369 RUN sed -i 's/localhost/corehost/g' voctogui/default-config.ini ;\
54 sed -i 's/system=gl/system=xv/g' voctogui/default-config.ini ;\
55 find /opt/voctomix/example-scripts/ -type f -exec sed -i 's/localhost/corehost/g' {} \;
70 sed -i 's/system=gl/system=xv/g' voctogui/default-config.ini ;\
71 find /opt/voctomix/example-scripts/ -type f -exec sed -i 's/localhost/corehost/g' {} \;
5672
57 ENTRYPOINT ["/opt/voctomix/docker-ep.sh"]
73 ENTRYPOINT ["/opt/voctomix/docker-entrypoint.sh"]
5874 CMD ["help"]
0 # Vocotomix Transitions
1
2 ## TOC
3 <!-- TOC depthFrom:2 depthTo:3 withLinks:1 updateOnSave:1 orderedList:0 -->
4
5 - [TOC](#toc)
6 - [Purpose](#purpose)
7 - [Use Cases](#use-cases)
8 - [Composites](#composites)
9 - [Transitions](#transitions)
10 - [Operations](#operations)
11 - [Composite Operations](#composite-operations)
12 - [Transition Operations](#transition-operations)
13 - [Interfaces](#interfaces)
14 - [Composites](#composites)
15 - [Transitions](#transitions)
16 - [Entities](#entities)
17 - [Transition](#transition)
18 - [Composite](#composite)
19 - [Frame](#frame)
20 - [Configuration](#configuration)
21 - [Configure Composites](#configure-composites)
22 - [Configure Transitions](#configure-transitions)
23 - [Using Transitions](#using-transitions)
24 - [Transition Tester](#transition-tester)
25 - [Example Usage](#example-usage)
26 - [Using verbose mode](#using-verbose-mode)
27 - [Code](#code)
28 - [TODO](#todo)
29 - [Future Development](#future-development)
30
31 <!-- /TOC -->
32
33 ## Purpose
34
35 The purpose of _voctomix_ __transitions__ is to implement an easy way to semi-automatize fading from one _video mixing scenario_ to another. We call those scenarios __composites__. A composite in _voctomix_ traditionally consists of up to two mixed video sources __A__ and __B__ whose representation parameters we call __frames__.
36
37 So far _voctomix_ was capable of using the following preset composites:
38
39 - single source __*c*(A)__
40 - single source full screen (fs)
41 - two sources __*c*(A,B)__
42 - side-by-side (sbs)
43 - picture-in-picture (pip)
44 - side-by-side-preview (sbsp)
45
46 Until transitions existed in _voctomix_, switching between any of these compositing scenarios was made rapidly from one frame to the next. The idea of transitions is to fade between composites by doing an animation and/or alpha (transparency) blending. With _voctomix_ __transitions__ we like to produce the most expected result for every possible scenario and give the user also the ability to create new composites and adjust or improve existing ones or even invent new transitions.
47
48 ## Use Cases
49
50 ### Composites
51
52 A __composite__ is a mix of multiple source images sources into a single destination image.
53 The _voctomix_ project was previously using four fixed composites called _fullscreen_, _picture-in-picture (pip)_, _side-by-side_ and _side-by-sidepreview_. We can divide these transitions into the following categories.
54 _Voctomix_ also uses a background source image which will not be discussed here because it is just irrelevant for handling transitions.
55
56 The images below show source <span style="color:red">__A__</span> in red and source <span style="color:blue">__B__</span> in blue.
57
58 #### c(A), c(B)
59
60 ![fullscreen A composite](doc/transitions/images/fullscreen.png)
61 ![fullscreen B composite](doc/transitions/images/fullscreen-b.png)
62
63 We name these kind of composites __c(A)__ or __c(B)__.
64
65 #### c(A,B)
66
67 ![pip composite](doc/transitions/images/pip.png)
68 ![sidebyside composite](doc/transitions/images/sidebyside.png)
69 ![sidebysidepreview composite](doc/transitions/images/sidebysidepreview.png)
70
71 We name these kind of composites __c(A,B)__.
72
73 We later also differ between overlapping and __non-overlapping composites__.
74 _pip_ for example is an __overlapping composite__ because the image of source B overlaps that of source A.
75
76 ### Transitions
77
78 Generally we can differ between the following transition cases.
79
80 #### *c*(A) &harr; *c*(B)
81
82 ![fullscreen-fullscreen transition](doc/transitions/images/fullscreen-fullscreen.gif)
83 ![another fullscreen-fullscreen transition](doc/transitions/images/fullscreen-fullscreen-both.gif)
84
85 First case is to switch from one full screen source to another by switching A &harr; B. The most common method here is to blend transparency of both sources from one to the other.
86
87 #### *c*(A) &harr; *c*(A,B)
88
89 ![fullscreen-pip transition](doc/transitions/images/fullscreen-pip.gif)
90 ![fullscreen-sidebyside transition](doc/transitions/images/fullscreen-sidebyside.gif)
91 ![fullscreen-sidebysidepreview transition](doc/transitions/images/fullscreen-sidebysidepreview.gif)
92
93 Switch from full screen to a composite of both sources can be done by blending the alpha channel of the added source from transparent to opaque or by an animation of the incoming source or both.
94
95 #### *c*(B) &rarr; *c*(A,B)
96
97 ![fullscreen-b-pip transition](doc/transitions/images/fullscreen-b-pip.gif)
98 ![fullscreen-b-sidebyside transition](doc/transitions/images/fullscreen-b-sidebyside.gif)
99 ![fullscreen-b-sidebysidepreview transition](doc/transitions/images/fullscreen-b-sidebysidepreview.gif)
100
101 #### *c*(A,B) &harr; *c*(B,A)
102
103 ![pip-pip transition](doc/transitions/images/pip-pip.gif)
104 ![sidebyside-sidebyside transition](doc/transitions/images/sidebyside-sidebyside.gif)
105 ![sidebysidepreview-sidebysidepreview transition](doc/transitions/images/sidebysidepreview-sidebysidepreview.gif)
106
107 To switch between A and B within a composite an animation is preferable. In some composites like _picture-in-picture_ (see in the middle) the second source (B) is overlapping the first one (A) and so the *z-order* (order in which the frames have to be drawn) has to be flipped within a transition to get a proper effect.
108
109 To guarantee that this is possible transitions can be improved by inserting so-called __intermediate composites__ which add __key frames__ for both sources in which they do not overlap and so bring a chance to do the z-order swap.
110 _voctomix_ __transitions__ is then using *B-Splines* to interpolate a smooth motion between __*c*(A,B)__ &harr; __*t'*(A,B)__ &harr; __*c*(B,A)__. You even can use multiple intermediate composites within the same transition, if you like.
111
112 #### *c<sub>1</sub>*(A,B) &harr; *c<sub>2</sub>*(A,B)
113
114 ![sidebyside-sidebysidepreview transition](doc/transitions/images/sidebyside-sidebysidepreview.gif)
115 ![sidebysidepreview-sidebyside transition](doc/transitions/images/sidebysidepreview-sidebyside.gif)
116 ![sidebyside-pip transition](doc/transitions/images/sidebyside-pip.gif)
117
118 Switching the composite while leaving the sources A and B untouched is similar to the previous case __*c*(A,B)__ &harr; __*c*(B,A)__ except that there is usually no need to have intermediate composites to switch the z-order because A and B remain unswapped.
119
120 #### *c<sub>1</sub>*(A,B) &harr; *c<sub>2</sub>*(B,A)
121
122 ![sidebyside-sidebysidepreview-b transition](doc/transitions/images/sidebyside-sidebysidepreview-b.gif)
123 ![sidebysidepreview-sidebyside-b transition](doc/transitions/images/sidebysidepreview-sidebyside-b.gif)
124 ![pip-sidebyside-b transition](doc/transitions/images/sidebyside-b-pip.gif)
125
126 #### Three-Sources Transitions
127
128 Switching a source of one frame within a composite to another (external) source leads to a *three-sources transition* because three sources have to be mixed together simultaneously.
129 To avoid the complexity of transitions of such a higher grade we like to stay with the real use cases which occur when working with *voctomix*.
130 We exclude all three sources transitions except those of one type.
131
132 ##### *c*(A<sub>1</sub>) &harr; *c*(A<sub>2</sub>)
133 When you start with a single source composite and you switch that one input source to another one which is not the hidden source B, you just have to switch source B to A<sub>2</sub> first and then do a two source transition *c*(A<sub>1</sub>) &harr; *c*(B) which is then equivalent to *c*(A<sub>1</sub>) &harr; *c*(A<sub>2</sub>).
134
135 ##### *c*(A) &harr; *c*(A,B<sub>2</sub>)
136
137 We easily can cover this one with the same switching maneuver as in the previous case.
138
139 ##### *c*(A<sub>1</sub>,B) &harr; *c*(A<sub>2</sub>,B)
140
141 The remaining one is different:
142 If you want to switch one of the sources within a two source composite to a new (external) source you need a real three sources transition to animate that case.
143
144 Again we like to restrict that issue to the real use case where we use just one type of three source transition which is based on a two source transitions and just mixes one source to another external by transparency fading.
145
146 This case is currently out of scope for transitions and is solved within the mixer.
147
148 ## Operations
149
150 To minimize the amount of composites and transitions which must be configured, missing composites and transitions shall be auto-generated.
151 The rules to do that are listed below.
152
153 Mostly these operations are inserted automatically.
154 They are described here for better understanding of how _voctomix_ **transitions** work and to understand the debugging output of the resulting code.
155
156 ### Composite Operations
157
158 #### Equivalence
159
160 A frame of empty size is invisible like one which is fully transparent but has an extent.
161 Also a composite of two frames where one is opaque and overlapping the other completely includes one invisible frame.
162 So those kind of composites may be treated as equivalent if one frame differs in both composites by some properties but is overall still invisible in both.
163
164 So when _voctomix_-**transitions** is looking for a matching composite it uses this rule which shortens the list of necessary manual transition definitions.
165
166 #### Swap
167
168 A composite of source A and B _c(A,B)_ can be swapped by simply swapping A and B like in this example:
169
170 ![sidebyside composite](doc/transitions/images/sidebyside.png)
171 ![sidebyside composite](doc/transitions/images/sidebyside-swapped.png)
172
173 We mark that swap operation with a `^` sign.
174 Put into a formula we can write this as
175
176 <center>
177 ^*c*(A,B) = *c*(A,B)
178 </center>
179
180 #### Gamma
181
182 The **gamma operation** is used for that one remaining *three sources transition* case when you switch one of the sources to another input within a two source composite.
183
184 Taking a two source composite *c*(A,B) and a third source C this operation has a transition *T* as result.
185
186 We describe that with the upper case Greek letter **Γ** as:
187
188 <center>
189 *T* = Γ(*c*,C) = Γ(*c*(A,B),C) = *c*(A,B) &rarr; *c*(A,C)
190 </center>
191 or
192 <center>
193 *T* = Γ(C,*c*) = Γ(C,*c*(A,B)) = *c*(A,B) &rarr; *c*(C,B)
194 </center>
195
196 Where *T* is the resulting transition, *c* is the current composite and *A*,*B* and *C* the related sources.
197
198 Because *voctomix* **transitions** are focusing on two sources composites and translations we will integrate that case in separately into the mixer.
199
200 ### Transition Operations
201
202 #### Reverse
203
204 ![sidebyside composite](doc/transitions/images/sidebysidepreview-sidebyside.gif)
205 ![sidebyside composite](doc/transitions/images/sidebyside-sidebysidepreview.gif)
206
207 A transition *T* from composite *c<sub>1</sub>* to composite *c<sub>2</sub>* written as...
208
209 *T* = *c<sub>1</sub>* &rarr; *c<sub>2</sub>*
210
211 ...can be reversed.
212
213 We mark that reverse operation with an exponent of <sup>`-1`</sup>:
214
215 *T*<sup>-1</sup> = (*c<sub>1</sub>*(A,B) &rarr; *c<sub>2</sub>*(A,B))<sup>-1</sup> = *c<sub>2</sub>*(A,B) &rarr; *c<sub>1</sub>*(A,B)
216
217 Or shorter:
218
219 *T*<sup>-1</sup> = (*c<sub>1</sub>* &rarr; *c<sub>2</sub>*)<sup>-1</sup> = *c<sub>2</sub>* &rarr; *c<sub>1</sub>*
220
221 #### Phi &Phi;()
222
223 This operation is needed to handle some transitions between _overlay composites_.
224 It works different because it does not change a transition but it's processing.
225 We call that operation _&Phi;()_.
226
227 Overlay composites have a so-called _z-order_ which defines that B is drawn above A.
228 If you take an overlay composite like _picture-in-picture_, generating an animation for swapping both sources must include a switch of this z-order.
229
230 This is done with the _&Phi;()_ operation which finds the first composite within a transition where source B do not even partially cover the image of source A.
231 To profit by this operation one must specialize this transition an put a non-overlaying composite between the target composites.
232
233 So to get a proper picture-in-picture &arr; picture-in-picture transition we can put a side-by-side composite between:
234
235 <center>
236 &Phi;(*pip* &harr; *sbs* &harr; *pip*)
237 </center>
238
239 The result with a side-by-side composite in the middle looks like:
240
241 ![pip-pip transition](doc/transitions/images/pip-pip-key.gif)
242
243 On the left you can see the added side-by-side composite as rectangles and you can see that A and B are swapping somewhere within the animation.
244
245 Without side-by-side in the middle it would look like:
246
247 ![pip-pip transition](doc/transitions/images/pip-pip-default.gif)
248
249 ...which is worse than a hard cut.
250
251 ## Interfaces
252
253 To use the following code you first need to import some stuff.
254
255 ```python
256 from transitions import Transitions, Composites
257 ```
258
259 Maybe importing `L`, `T`, `R` and `B` is a good idea too because it can be helpful when accessing the coordinates of the resulting animation frames.
260 `X` and `Y` can be used to access width and height in `size`.
261
262
263 ### Composites
264
265 `Composites` (plural) is a python class of the preferred interface to _voctomix_ __composites__ and includes the following function:
266
267 #### Composites.configure()
268 Reads a configuration and returns all included composites.
269 Take that return value and give it to `Transitions.configure()` to load the transitions configuration.
270 You may also use the included composites to set up your compositor's switching capabilities - for example in the user interface.
271 ```python
272 def configure(cfg, size):
273 ```
274 Additionally you have to give `size` which must be a list including _width_ and _height_ in pixels of both source frames.
275 It is an external parameter and should be given by your compositor configuration.
276 `size` will be used to calculate coordinates from any proportional floating point values inside the configuration and for calculating source related coordinates of any cropping.
277
278 The return value is a dictionary of `string` &rarr; `Composite`.
279
280 `configure()` may throw an `RuntimeError` exception when parsing the syntax causes problems.
281
282 In *future development* this could also take different `size` values for each source too.
283
284 ### Transitions
285
286 `Transitions` holds a transition table from all configured target composites to each other.
287
288 #### Transitions.configure()
289 Reads a configuration and returns all included transitions.
290 Take that return value and give it to `find()` to fetch a specific transition.
291 ```python
292 def configure(cfg, composites, fps=25):
293 ```
294 Generates all transitions configured by the list of named configuration values in dictionary `cfg` (`string` &rarr; `string`) by using the given `composites` and `fps` (frames per second) and return them in a dictionary of `string` &rarr; `Transition`.
295
296 `configure()` may throw an `RuntimeError` exception when parsing the syntax causes problems.
297
298 #### Transitions.add()
299 This method is mainly used internally by `config()` but you also can use it to add transitions manually to the transition table.
300 ```python
301 def add(self, transition, frames, overwrite=False):
302 ```
303 `transition` is added to at all position in the table where it matches.
304 `frames` is the number of frames the (re-)calculated transition shall last.
305 When `overwrite` is `False` existing transitions will not be overwritten.
306
307 #### Transitions.find()
308 Fetch a transition whose beginning and ending is matching the given composites.
309 ```python
310 def find(begin, end):
311 ```
312 Searches in the given dictionary `transitions` for a transition that fades `begin` to `end`.
313 In a second step also checks if reversed versions transitions match.
314 If a transition was found a tuple of it's name and the transition will be returned - otherwise `None`.
315
316 #### Transitions.travel()
317 Returns a list of pairs of composites along all possible transitions between all given `composites` by walking the tree of all combinations recursively.
318 ```python
319 def travel(composites, previous=None):
320 ```
321 Parameter `previous` shall always be the default `None` and must only be used internally for recursion.
322 This method is just a tool to walk all possible transitions in one animation and so concatenate existing transitions.
323
324 Currently it is only used within the _Transition Tester_ to generate test output but could be also subject of *future development* to generate more complex animations by concatenation.
325
326 ## Entities
327
328 ### Transition
329
330 A transition consists of a list of composites.
331 These composites can be either:
332 - two or more in a list of __key composites__ to generate an animation for
333 - or a list of composites which describe an already generated animation and so a ready-to-go transition.
334
335 #### Transition.frames()
336 Returns the number of composites stored in this transition.
337 ```python
338 def frames(self):
339 ```
340 The return value can be either the _number of key frames_ or the _number frames of an animation_ depending on if this transition instance is meant to be used as a parameter to calculate an animation or as return value of that calculation.
341
342 #### Transition.A/B()
343 Returns one frame of the sources A or B which shall be realized within your compositor.
344 ```python
345 def A(self, n):
346 def B(self, n):
347 ```
348 Precisely returns `Frame` number `n` of source A or B of the `Transtition`.
349
350 #### Transition.flip()
351 Return the index of the frame preferred to flip both sources (and the scenario) to get a proper z-order behavior.
352 ```python
353 def flip(self):
354 ```
355 Using this information is strongly recommended to get smooth results, when using transitions of type *c*(A,B) &harr; *c*(B,A).
356
357 #### Transition.begin/end()
358 Returns the begin or end composite of that transition.
359 ```python
360 def begin(self):
361 def end(self):
362 ```
363
364 #### Transition.name()
365 Returns the name of that transition.
366 ```python
367 def name(self):
368 ```
369
370 ### Composite
371
372 A `Composite` instance includes two frames for source A and B and includes no functions you need to know about to use _voctomix_ __transitions__.
373
374 #### Composite.inter
375 Marks intermediate composites if `True`.
376 ```
377 self.inter = False
378 ```
379 Intermediate composites can be marked by this flag to avoid their appearance in an UI selection for example.
380
381 ### Frame
382
383 A __Frame__ includes all the information necessary to set up a single source in your compositor:
384
385 #### Frame.rect
386 Returns the dimensions in pixels of this frame.
387 ```python
388 self.rect = [0, 0, 0, 0]
389 ```
390 The value is a list of coordinates of the _left_, _top_, _right_ and _bottom_ of the frame.
391 Use imported constants `L`, `T`, `R` and `B` to access these list elements.
392 The default is an empty rectangle in the upper-left corner.
393
394 #### Frame.alpha
395 The transparency value of this frame.
396 ```python
397 self.alpha = 255
398 ```
399 A value between `0` and `255` where `0` means invisible, `255` means opaque and values between describe the corresponding semi-transparency.
400
401
402 #### Frame.crop
403 The source's cropping values which may change within a transition animation.
404 ```python
405 self.crop = [0, 0, 0, 0]
406 ```
407 The values are the _left_, _top_, _right_ and _bottom_ cropping meant as distance from the frame's originally borders.
408 Because cropping is a property of the video source these values will be related to the source size given to `Composites.configure()`.
409
410 #### Frame.key
411 This member is `True` if this is a key frame loaded from the configuration or `False` if it was generated by interpolation.
412 ```python
413 self.key = False
414 ```
415
416 #### Frame.cropped()
417 Returns the cropped rectangle of the frame related to the current size of the frame.
418 ```python
419 def cropped(self):
420 ```
421 Use this to get the resulting frame's extent which will be visible in the image mixed by your compositor.
422
423
424 ## Configuration
425
426 Configuration is done with an INI file like all the other _voctomix_ configuration.
427
428 Additionally we like to leave the configuration as easy and compatible as possible to former configurations.
429 Because we also like to increase the flexibility, any unification of the composites does indeed lead to a quite different configuration format.
430 To keep migration easy the basic options and values are mostly just reordered or renamed.
431
432 ### Configure Composites
433
434 List of configurations of custom named composites for mixing video sources A and B.
435
436 Attribute | Format | Default | Description
437 ----------------|--------|-------------|-------------------------------------
438 _name_`.a` | RECT | no output | position and size of frame A
439 _name_`.b` | RECT | no output | position and size of frame B
440 _name_`.crop-a` | CROP | no cropping | cropping borders of frame A
441 _name_`.crop-b` | CROP | no cropping | cropping borders of frame B
442 _name_`.alpha-a`| ALPHA | opaque | opacity of frame A
443 _name_`.alpha-b`| ALPHA | opaque | opacity of frame B
444 _name_`.noswap` | BOOL | swap | prevents to target swapped composite
445
446 So a default frame without any attributes is invisible by his zero extent.
447
448 #### _name_
449 All attributes begin with the composite's name followed by a dot `.` and the attribute's name.
450 A composite can be freely named but _name_ must be unique.
451
452 #### Absolute and Proportional Coordinates
453
454 In __RECT__ and __CROP__ you may decide if you like to use _absolute pixel coordinates_ or _proportional floating point values_.
455 Using proportional values is often an advantage because you can easily change the full screen size once and all other pixel values will be automatically calculated with that size.
456 This enables you to use the same composites configuration with different resolutions but similar aspect ratio.
457
458 #### RECT
459 Rectangular coordinates are given in different formats like: `X/Y WxH`, `POS WxH`, `X/Y SIZE`, `POS SIZE` or `*`.
460
461 Whereat `X`,`Y`,`W`,`H` can be mixed integer pixel coordinates or float proportions.
462 `POS` and `SIZE` need to be float proportions.
463 `*` stands for full screen and inserts the `size` which was once given to `Composites.configure()`.
464
465 ##### Examples
466 ```ini
467 c.a = 10/10 100x100 ; source A pixel dimensions with format 'X/Y WxH'
468 ```
469 ```ini
470 c.a = 0.4 0.2 ; source A float proportions with format 'XY WH'
471 ```
472 ```ini
473 c.a = 0.1/10 0.9 ; stupid mixup with format 'X/Y WH'
474 c.b = * ; source B becomes full screen
475 ```
476
477 #### CROP
478 Cropping borders which are given by either `L/T/R/B`, `LR/TB` or `LRTB`
479
480 `L`,`T`,`R`,`B`, `LR`,`TB` and `LRTB` can be mixed integer absolute coordinates or float
481 proportions.
482
483 ##### Examples
484 ```ini
485 c.crop-a = 0/100/0/100 ; source A pixel cropping borders with format 'L/T/R/B'
486 ```
487 ```ini
488 c.crop-a = 0.0/0.2 ; source A float proportional borders with format 'LR/TB'
489 ```
490 ```ini
491 c.crop-b = 0.1 ; source B 10% from each border in format 'LRTB'
492 ```
493
494 #### ALPHA
495
496 Integer value in the range between `0` (invisible) and `255` (opaque) or float value between `0.0` (invisible) and `1.0` (opaque) or `*` (also opaque).
497
498 #### BOOL
499
500 Any value is true but false if empty.
501
502 ##### Examples
503 ```ini
504 c.alpha-a = * ; opaque source A using '*'
505 c.alpha-b = 0.5 ; 50% semitransparent source B as float
506 ```
507
508 ### Configure Transitions
509
510 The configuration of a transition is more easy.
511 List all transitions in an ini section like `[transitions]`.
512 Each one can be freely named and describes a timespan and a list of composites which will be processed into an animation. Interpolation will be linear with two composites and B-Splines for more.
513
514 ```ini
515 my_transition = 1000, pip / sidebyside
516 ```
517 This generates a linear transition from composite `pip` to composite `sidebyside` lasting one second (`1000` milliseconds).
518 ```ini
519 my_transition = 1500, pip / sidebyside / pip
520 ```
521 This generates a B-Spline transition from composite `pip` to composite `sidebyside` to composite `pip` (with A and B automatically swapped) with a duration of one and a half seconds (`1500` milliseconds).
522
523 ## Using Transitions
524
525 ```python
526 from transitions import Composites, Transitions, L, T, R, B
527 from configparser import SafeConfigParser
528 from out_of_scope import update_my_compositor
529
530 # set frame size
531 size = [1920, 1080]
532 # set frames per second
533 fps = 25
534 # load INI files
535 config = SafeConfigParser()
536 config.read(filename)
537 # read composites config section
538 composites = Composites.configure(config.itemc('composites'), size)
539 # read transitions config section
540 transitions = Transitions.configure(config.itemc('transitions'), composites, fps)
541
542 # search for a transitions that does a fade between fullscreen and sidebyside composites
543 t_name, t = Transitions.find(composites["fullscreen"], composites["sidebyside"], transitions)
544 # stupid loop through all frames of the animation
545 for i in range(t.framec()):
546 # access current frame in animation to update compositing scenario
547 update_my_compositor( t.A(i), t.B(i) )
548 ```
549
550 ## Transition Tester
551
552 The transitions tester lists information about what composites and transitions are defined in a configuration called `composite.ini` and generates PNG files or animated GIF for each listed transition.
553 You may also select additional drawing of cropping, key frames or a title by command line option or take a further look into the calculations by using verbose mode.
554
555 ```raw
556 ▶ python3 testtransition.py -h
557 usage: testtransition.py [-h] [-m] [-l] [-g] [-t] [-k] [-c] [-C] [-r] [-n]
558 [-P] [-L] [-G] [-v]
559 [composite [composite ...]]
560
561 transition - tool to generate voctomix transition animations for testing
562
563 positional arguments:
564 composite list of composites to generate transitions between (use all
565 available if not given)
566
567 optional arguments:
568 -h, --help show this help message and exit
569 -m, --map print transition table
570 -l, --list list available composites
571 -g, --generate generate animation
572 -t, --title draw composite names and frame count
573 -k, --keys draw key frames
574 -c, --corners draw calculated interpolation corners
575 -C, --cross draw image cross through center
576 -r, --crop draw image cropping border
577 -n, --number when using -g: use consecutively numbers as file names
578 -P, --nopng when using -g: do not write PNG files (forces -G)
579 -L, --leave when using -g: do not delete temporary PNG files
580 -G, --nogif when using -g: do not generate animated GIFS
581 -v, --verbose also print WARNING (-v), INFO (-vv) and DEBUG (-vvv)
582 messages
583
584 ```
585
586 ### Example Usage
587
588 ```
589 ▶ python3 testtransition.py -lvgCctk pip
590 1 targetable composite(s):
591 pip
592 1 intermediate composite(s):
593 fullscreen-pip
594 saving transition animation file 'pip-pip.gif' (pip-pip, 37 frames)...
595 1 transitions available:
596 pip-pip
597 ```
598
599 This call generates the following animated GIF:
600
601 ![pip-pip transition with keyframes](doc/transitions/images/pip-pip-key-big.gif)
602
603 You can see the key frames of `pip` `A.0`=`B.2` and `B.0`=`A.2` of the start and end composite. In the vertical center you can see the key frames `A.1` and `B.1` given by `sidebyside` to produce a moment of non-overlapping. At the first time when the blue frame `B` is not overlapping the red one `A` the flipping point is reached and sources `A`/`B` can be flipped without side effects.
604
605 The following configuration file was used to generate that animation:
606
607 ```ini
608 [output]
609 ; full screen size in pixels
610 size = 960x540
611 ; frames per second to render
612 fps = 25
613
614 [composites]
615 ; Frame A full screen
616 pip.a = *
617 ; frame B lower-right corner with 16% size
618 pip.b = 0.83/0.82 0.16
619
620 ; left-middle nearly half size
621 sidebyside.a = 0.008/0.25 0.49
622 ; right-middle nearly half size
623 sidebyside.b = 0.503/0.25 0.49
624
625 [transitions]
626 ; transition from pip to pip (swapped) over sidebyside within 1.5 seconds
627 pip-pip = 1500, pip / sidebyside / pip
628 ```
629
630 ### Using verbose mode
631
632 In verbose mode you can see more information about how a transition will be found and what it's data looks like:
633
634 ```raw
635 ▶ python3 testtransition.py -vvv pip
636 reading composites from configuration...
637 read 2 composites:
638 sbs
639 pip
640 reading transitions from configuration...
641 adding transition pip-pip = pip -> pip
642 pip-pip = pip -> pip:
643 No. Key A( L, T R, B alpha LCRP,TCRP,RCRP,BCRP XZOM,YZOM) B( L, T R, B alpha LCRP,TCRP,RCRP,BCRP XZOM,YZOM) Name
644 0 * A( 0, 0 240, 135 255 0, 0, 0, 0 0.00,0.00) B( 199, 110 237, 131 255 0, 0, 0, 0 0.00,0.00) pip
645 1 * A( 1, 33 118, 99 255 0, 0, 0, 0 0.00,0.00) B( 120, 33 237, 99 255 0, 0, 0, 0 0.00,0.00) sbs
646 2 * A( 0, 0 240, 135 255 0, 0, 0, 0 0.00,0.00) B( 199, 110 237, 131 255 0, 0, 0, 0 0.00,0.00) pip
647 calculating transition pip-pip = pip/sbs/pip
648 read 1 transition(s):
649 pip-pip
650 using 1 target composite(s):
651 pip
652 generated sequence (2 items):
653 pip
654 pip
655 request transition (1/1): pip → pip
656 transition found: Φ(pip-pip)
657 Φ(pip-pip) = pip -> pip:
658 No. Key A( L, T R, B alpha LCRP,TCRP,RCRP,BCRP XZOM,YZOM) B( L, T R, B alpha LCRP,TCRP,RCRP,BCRP XZOM,YZOM) Name
659 0 * A( 0, 0 240, 135 255 0, 0, 0, 0 0.00,0.00) B( 199, 110 237, 131 255 0, 0, 0, 0 0.00,0.00) pip
660 1 A( 0, 0 239, 135 255 0, 0, 0, 0 0.00,0.00) B( 199, 110 237, 131 255 0, 0, 0, 0 0.00,0.00) ...
661 2 A( 0, 0 238, 134 255 0, 0, 0, 0 0.00,0.00) B( 198, 108 238, 130 255 0, 0, 0, 0 0.00,0.00) ...
662 3 A( 4, 0 234, 130 255 0, 0, 0, 0 0.00,0.00) B( 196, 106 240, 131 255 0, 0, 0, 0 0.00,0.00) ...
663 4 A( 9, 0 228, 123 255 0, 0, 0, 0 0.00,0.00) B( 193, 101 245, 130 255 0, 0, 0, 0 0.00,0.00) ...
664 5 A( 15, 1 219, 116 255 0, 0, 0, 0 0.00,0.00) B( 188, 95 249, 129 255 0, 0, 0, 0 0.00,0.00) ...
665 6 A( 21, 2 208, 107 255 0, 0, 0, 0 0.00,0.00) B( 182, 87 254, 127 255 0, 0, 0, 0 0.00,0.00) ...
666 7 A( 24, 4 194, 100 255 0, 0, 0, 0 0.00,0.00) B( 175, 79 258, 126 255 0, 0, 0, 0 0.00,0.00) ...
667 8 A( 27, 6 180, 92 255 0, 0, 0, 0 0.00,0.00) B( 167, 70 261, 123 255 0, 0, 0, 0 0.00,0.00) ...
668 9 A( 26, 9 164, 87 255 0, 0, 0, 0 0.00,0.00) B( 157, 60 260, 118 255 0, 0, 0, 0 0.00,0.00) ...
669 10 A( 20, 13 147, 84 255 0, 0, 0, 0 0.00,0.00) B( 145, 50 256, 112 255 0, 0, 0, 0 0.00,0.00) ...
670 ----------------------------------------------------------- FLIP SOURCES ------------------------------------------------------------
671 11 B( 11, 20 130, 87 255 0, 0, 0, 0 0.00,0.00) A( 133, 41 248, 106 255 0, 0, 0, 0 0.00,0.00) ...
672 12 * B( 1, 33 118, 99 255 0, 0, 0, 0 0.00,0.00) A( 120, 33 237, 99 255 0, 0, 0, 0 0.00,0.00) sbs
673 13 B( 1, 32 118, 98 255 0, 0, 0, 0 0.00,0.00) A( 119, 32 236, 98 255 0, 0, 0, 0 0.00,0.00) ...
674 14 B( 10, 51 125, 116 255 0, 0, 0, 0 0.00,0.00) A( 104, 24 223, 91 255 0, 0, 0, 0 0.00,0.00) ...
675 15 B( 30, 64 141, 126 255 0, 0, 0, 0 0.00,0.00) A( 88, 17 215, 88 255 0, 0, 0, 0 0.00,0.00) ...
676 16 B( 55, 74 158, 132 255 0, 0, 0, 0 0.00,0.00) A( 72, 11 210, 89 255 0, 0, 0, 0 0.00,0.00) ...
677 17 B( 80, 83 174, 136 255 0, 0, 0, 0 0.00,0.00) A( 57, 7 210, 93 255 0, 0, 0, 0 0.00,0.00) ...
678 18 B( 106, 90 189, 137 255 0, 0, 0, 0 0.00,0.00) A( 43, 4 213, 100 255 0, 0, 0, 0 0.00,0.00) ...
679 19 B( 131, 96 203, 136 255 0, 0, 0, 0 0.00,0.00) A( 30, 2 217, 107 255 0, 0, 0, 0 0.00,0.00) ...
680 20 B( 154, 101 215, 135 255 0, 0, 0, 0 0.00,0.00) A( 19, 1 223, 116 255 0, 0, 0, 0 0.00,0.00) ...
681 21 B( 172, 105 224, 134 255 0, 0, 0, 0 0.00,0.00) A( 11, 0 230, 123 255 0, 0, 0, 0 0.00,0.00) ...
682 22 B( 187, 107 231, 132 255 0, 0, 0, 0 0.00,0.00) A( 5, 0 235, 130 255 0, 0, 0, 0 0.00,0.00) ...
683 23 B( 195, 109 235, 131 255 0, 0, 0, 0 0.00,0.00) A( 1, 0 239, 134 255 0, 0, 0, 0 0.00,0.00) ...
684 24 * B( 199, 110 237, 131 255 0, 0, 0, 0 0.00,0.00) A( 0, 0 240, 135 255 0, 0, 0, 0 0.00,0.00) pip
685 ```
686
687 As you can see the triple verbose mode (using option `-vvv`) prints out a list of loaded composites and found transitions too, like option `-l` does.
688
689 Additionally it prints out:
690 - the composites used to request the transition `pip -> pip` including a mark `(swapped)` at the right margin indicates that this transition has the same begin and end frame and so it will be swapped,
691 - the searched transitions (in this case there is only one),
692 - the long table which shows the calculated animation for this transition and all it's properties,
693 - the _flipping point_ at `--- FLIP SOURCES ---` from which on the letters for A and B are swapped and
694 - also the `*` signs which mark the used key frames from the composites out of the configuration.
695
696 #### Transition Table
697
698 To examine the automatically generated composites and transitions you can print out the transition table with `-m`:
699
700 ```
701 ▶ python3 testtransition.py -m
702 transition table:
703 fs-a pip sbs sbsp fs-b ^sbsp ^sbs
704
705 fs-a Φ(def(fs-a/fs-a)) fs-a-pip fs-a-sbs fs-a-sbsp fs-fs ^fs-b-sbsp ^fs-b-sbs
706 pip fs-a-pip⁻¹ Φ(pip-pip) def(pip/sbs) def(pip/sbsp) fs-b-pip⁻¹ def(pip/^sbsp) def(pip/^sbs)
707 sbs fs-a-sbs⁻¹ def(sbs/pip) Φ(sbs-sbs) def(sbs/sbsp) fs-b-sbs⁻¹ def(sbs/^sbsp) Φ(_sbs-sbs⁻¹)
708 sbsp fs-a-sbsp⁻¹ def(sbsp/pip) def(sbsp/sbs) Φ(def(sbsp/sbsp)) fs-b-sbsp⁻¹ Φ(def(sbsp/^sbsp)) def(sbsp/^sbs)
709 fs-b Φ(fs-fs⁻¹) Φ(fs-b-pip) fs-b-sbs fs-b-sbsp Φ(^fs-fs) ^fs-a-sbsp ^fs-a-sbs
710 ^sbsp ^fs-b-sbsp⁻¹ def(^sbsp/pip) def(^sbsp/sbs) Φ(def(^sbsp/sbsp)) ^fs-a-sbsp⁻¹ Φ(def(^sbsp/^sbsp)) def(^sbsp/^sbs)
711 ^sbs ^fs-b-sbs⁻¹ def(^sbs/pip) Φ(_sbs-sbs) def(^sbs/sbsp) ^fs-a-sbs⁻¹ def(^sbs/^sbsp) Φ(^sbs-sbs)
712
713 ```
714
715 The table has rows of all configured start target composites and columns of end target composites.
716 Every cell includes the available transitions.
717
718 ### Code
719
720 #### main program
721 The program consists of several functions which are called from a main block:
722
723 ```python
724 read_argumentc()
725 init_log()
726 render_sequence(*read_config("composite.ini"))
727 ```
728 `render_sequence()` takes exactly what `read_config()` is delivering.
729
730 #### read_argumentc()
731 Reads command line arguments like described above.
732 ```python
733 def read_argumentc():
734 ```
735 Fills global `Args` with the parser result.
736
737 #### init_log()
738 Initializes debug logging.
739 ```python
740 def init_log():
741 ```
742 Global `log` gets the logger instance.
743
744 #### read_config()
745 Read from the given file.
746 ```python
747 def read_config(filename):
748 ```
749 `filename` is the name of the configuration file.
750
751 #### render_compositec()
752
753 Renders pictures of all `composites` and saves them into PNG files.
754
755 ```python
756 def render_compositec(size, composites):
757 ```
758 Produces images of the given `size`.
759
760 #### render_sequence()
761
762 Render all transitions between all items in the given sequence by using `safe_transition_gif()` (see below).
763
764 ```python
765 def render_sequence(size, fps, targets, transitions, composites):
766 ```
767
768 Sequence is defined by the names listed in `targets`.
769 Produces images of the given `size`.
770 Calculate with `fps` frames per second and use the `transitions` and `composites` dictionaries to find matching transitions.
771
772 #### save_transition_gif()
773
774 Generates an animated GIF of the given name of an animation by using `draw_transition()` (see below)
775
776 ```python
777 def save_transition_gif(filename, size, name, animation, time):
778 ```
779
780 `filename` is the name of the resulting file, `size` it's dimensions, `name` the displayed title, `animation` the transition to render and `time` the duration of that whole animation in the GIF.
781
782 #### draw_composite()
783 Function that draws one composite and returns an image.
784
785 ```python
786 def draw_composite(size, composite, swap=False):
787 ```
788
789 Produces images of `composite` in the given `size` which can be drawn swapped by using `swap`.
790
791 #### draw_transition()
792 Internal function that draws one transition and returns a list of images.
793
794 ```python
795 def draw_transition(size, transition, name=None):
796 ```
797
798 Produces images of `transition` in the given `size`.
799
800 ## TODO
801 #### Integration into exisiting _voctomix_
802
803 To get out video transition effect within _voctomix_ the configuration needs a format update and the compositor must be extended by the ability to switch the compositing scenario quickly frame by frame synchronized with the play time.
804
805 #### Review the coordinate formats
806 ...in RECT and CROP so we do not hurt someone's feelings.
807
808 ### Future Development
809
810 - May be have just one `configure()` in `Transitions` which returns both composites and transitions so that you only need to import the Transitions interface instead of additionally the composites interface.
811 - Decide in which way three source scenarios like *c*(A<sub>1</sub>,B) &harr; *c*(A<sub>2</sub>,B) or *c*(A,B<sub>1</sub>) &harr; *c*(A,B<sub>2</sub>) can profite from any kind of specialized transitions.
812 - What about unlimited sources?
813 - add additional composite operations (like visual mirroring)?
0 # Voctomix [![Build Status](https://travis-ci.org/voc/voctomix.svg?branch=master)](https://travis-ci.org/voc/voctomix)
1 The [C3VOC](https://c3voc.de/) creates lecture recordings from German hacker/tech conferences. In the past we have used [dvswitch](http://dvswitch.alioth.debian.org/wiki/) very successfully but it has some serious limitations. Therefore we started looking for a replacement in 2014. We tested [snowmix](http://sourceforge.net/projects/snowmix/) and [gst-switch](https://github.com/timvideos/gst-switch) and while both did some things we wanted right, we realised that no existing tool would be able to fulfil all our wishes. Furthermore both are a nightmare to extend. So we decided to build our own implementation of a Live-Video-Mixer.
0 # VOC2MIX
21
3 ## Subprojects
4 The Voctomix project consists of three parts:
5 - [Voctocore](./voctocore/), the videomixer core-process that does the actual video- and audio crunching
6 - [Voctogui](./voctogui/), a GUI implementation in GTK controlling the core's functionality and giving visual feedback of the mixed video
7 - Voctomix [Example Scripts](./example-scripts/), a collection of tools and examples for talking to the core-process, feeding and receiving video-streams and controlling operations from scripts or command-line.
2 ## Current Documentation
83
9 ## Installation
10 Voctomix requires a fairly recent Version of GStreamer (at least 1.5, though we recommend 1.6 and later). This is natively present on [Debian Stretch](https://packages.debian.org/stretch/libgstreamer1.0-0) or [Sid](https://packages.debian.org/sid/libgstreamer1.0-0) and [Ubuntu Wily](http://packages.ubuntu.com/wily/libgstreamer1.0-0). On these systems it should run out of the box. We recommend using one of them. A [Docker](http://www.docker.com) image that uses Ubuntu Wily as a base is bundled with Voctomix. Please refer to the seperate [readme](./README_DOCKER.md) how to use the Docker image.
11
12 Install the required dependencies:
13 ````
14 # Requirements
15 apt-get install gstreamer1.0-plugins-bad gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools libgstreamer1.0-0 python3 python3-gi gir1.2-gstreamer-1.0 gir1.2-gst-plugins-base-1.0
16
17 # Optional for the Example-Scripts
18 apt-get install python3-pyinotify gstreamer1.0-libav rlwrap fbset ffmpeg netcat gstreamer1.0-vaapi
19 ````
20
21
22 For the GUI you'll -- additionally to a gnome-desktop -- need to install the following dependencies:
23 ````
24 apt-get install gstreamer1.0-plugins-bad gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-alsa gstreamer1.0-tools libgstreamer1.0-0 python3 python3-gi python3-gi-cairo gir1.2-gstreamer-1.0 gir1.2-gst-plugins-base-1.0 gir1.2-gtk-3.0
25 ````
26
27 Now you should be able to clone the git-repository and run Voctomix or the GUI like this:
28 ````
29 git clone https://github.com/voc/voctomix.git
30 cd voctomix
31 ./voctocore/voctocore.py -vv
32 ./voctogui/voctogui.py -vv
33 ````
34
35 ## Quickstart using Docker
36
37 ### docker-compose
38
39 Install docker-compose (prerequisite)
40 ```
41 apt install docker-compose # debian
42 dnf install docker-compose # fedora
43 ```
44
45 Run the core, gui and two example source streams
46 ```
47 xhost +local:$(id -un)
48 mkdir /tmp/vocto/ && touch /tmp/vocto/configgui.ini
49 GID=$(id -g) UID=$(id -u) docker-compose up
50 ```
51
52 Run only the core
53 ```
54 docker-compose up voctocore
55 ```
56
57 Clean up stale containers
58 ```
59 docker-compose rm
60 ```
61
62 ### Manually
63
64 Run the core and two example source streams
65 ```
66 docker run -it --rm --name=voctocore c3voc/voctomix core
67 docker run -it --rm --name=cam1 --link=voctocore:corehost c3voc/voctomix gstreamer/source-videotestsrc-as-cam1.sh
68 docker run -it --rm --name=bg --link=voctocore:corehost c3voc/voctomix gstreamer/source-videotestsrc-as-background-loop.sh
69 ```
70
71 Run the GUI
72 ```
73 xhost +local:$(id -un)
74 mkdir /tmp/vocto/ && touch /tmp/vocto/configgui.ini
75 docker run -it --rm --name=gui --env=gid=$(id -g) --env=uid=$(id -u) --env=DISPLAY=:0 --link=voctocore:corehost \
76 -v /tmp/vocto/configgui.ini:/opt/voctomix/voctogui/config.ini -v /tmp/.X11-unix:/tmp/.X11-unix -v /tmp/.docker.xauth:/tmp/.docker.xauth c3voc/voctomix gui
77 ```
78
79 show more commands availabe
80 ```
81 docker run -it --rm c3voc/voctomix help
82 docker run -it --rm c3voc/voctomix examples
83 ```
84
85 ## A word on CPU usage
86 Voctomix requires a fair amount of CPU time to run in the default configuration of 1920×1080 at 25fps. Our production systems have these CPUs: `Intel Core i7-3770 CPU 4x 3.40GHz` but we're also experimenting with newer ones like these: `Intel Core i7-6700K, 4x 4.00GHz`.
87 For testing and development you may want to use a `config.ini` that reduces the resolution and also turns off the JPEG preview encoders, which take a huge amount of the required CPU power and are not required, as long as the GUI and the Core run on the same machine (or have a 10GE Link between them, FWIW). Don't forget to modify your source scripts to provide the correct resolution.
88
89 Such a config.ini might look like this:
90 ````
91 [mix]
92 videocaps=video/x-raw,format=UYVY,width=320,height=180,framerate=25/1,pixel-aspect-ratio=1/1
93
94 [previews]
95 enabled=false
96 videocaps=video/x-raw,width=320,height=180,framerate=25/1
97 ````
98
99 ## A word on running in a VM
100 While the Core runs fine inside any VM, the GUI uses OpenGL to display the video streams. Don't forget to enable 3D acceleration in your VM to support this.
101
102
103 ## Contact
104 To get in touch with us we'd ask to join `#voctomix` on the hackint IRC network, mail to `voc AT c3voc DOT de` or meet us on one of the [many conferences](https://c3voc.de/eventkalender) we're at.
105 You may also want to watch MaZderMinds [talk](https://media.ccc.de/v/froscon2016-1696-voctomix) about Voctomix and [a presentation](https://media.ccc.de/v/froscon2015-1520-conference_recording_und_streaming) some of us gave about C3VOCs video infrastructure.
4 - [Core](https://github.com/voc/voctomix/tree/voctomix2/voctocore)
5 - [UI](https://github.com/voc/voctomix/tree/voctomix2/voctogui)
6 - [Transitions](https://github.com/voc/voctomix/blob/voctomix2/README-TRANSITIONS.md)
0 [mix]
1 sources = CAM1,CAM2,LAPTOP
2
3 [source.CAM1]
4 # grab this source from Decklink card
5 kind=decklink
6 # set video scan mode to 'progressive segmented frame'
7 # tested: Panasonic AVC-CAM (AG-AC160-AEI) at SDI, 1080i/720p (PSF)
8 scan = psf
9
10 [previews]
11 ; enable previews so we can see something in VOC2GUI
12 enabled = true
13
14 [composites]
15 ; fullscreen source B is full transparent
16 FULL.alpha-b = 0
17
18 [transitions]
19 ; unique name = ms, from / [... /] to
20 FADE = 750, FULL / FULL
0 [mix]
1 sources = CAM1,CAM2,LAPTOP
2
3 [previews]
4 ; enable previews so we can see something in VOC2GUI
5 enabled = true
6 ; enable live preview so we can see the blinder working
7 live = true
8
9 [blinder]
10 ; enable live stream blinding
11 enabled = true
12 ; name a blinding source 'PAUSE'
13 ;videos = PAUSE
14
15 [composites]
16 ; fullscreen source B is full transparent
17 FULL.alpha-b = 0
18
19 [transitions]
20 ; unique name = ms, from / [... /] to
21 FADE = 750, FULL / FULL
0 [mix]
1 #videocaps = video/x-raw,format=I420,width=1920,height=1080,framerate=25/1,pixel-aspect-ratio=1/1,interlace-mode=progressive
2 audiocaps = audio/x-raw,format=S16LE,channels=8,layout=interleaved,rate=48000
3
4 ; tcp listening ports will be 16000,16001,...
5 sources = cam1,cam2,cam3,grabber
6
7 [source.cam1]
8 audio.original = 0+1
9
10 [source.cam2]
11 audio.english = 0+1
12 audio.french = 2+3
13
14 [source.grabber]
15 audio.laptop = 0+1
16
17 [previews]
18 enabled = true
19 live = true
20 vaapi=h264
21 videocaps=video/x-raw,width=1024,height=576,framerate=25/1
22
23 [blinder]
24 enabled = true
25 sources = pause
26
27 [source.blinder]
28 audio.original = 0+1
29 audio.english = 0+1
30 audio.french = 0+1
31 audio.laptop = 0+1
32
33 [overlay]
34 ; default selection for overlay image
35 file = watermark.png|Watermark
36 ; user selection of overlay images
37 files = transparency.png|Transparency Test,watermark|Watermark,../voc2bg.png|35c3 Background
38 path = ./data/images/overlays
39
40 ; read user selection from schedule.xml file
41 schedule=schedule.xml
42 ; filter by room
43 room=HALL 1
44 ; filter by event ID (good for testing)
45 ;event=3
46 ; should the user be able to toggle the AUTO-OFF button?
47 user-auto-off = true
48 ; should the AUTO-OFF button be initially be off?
49 ;auto-off = false
50 ; set fading time when showing or hiding overlay
51 ;blend-time=300
52
53 [composites]
54 ; fullscreen source A (B is full transparent)
55 fs.a = *
56 fs.b = *
57 fs.alpha-b = 0
58 fs.noswap = true
59
60 ; fullscreen source A (facing picture-in-picture)
61 fs-pip.a = *
62 fs-pip.b = 0.86/0.85 0.0
63 fs-pip.alpha-b = 0
64 fs-pip.inter = true
65 fs-pip.mirror = true
66
67 ; fullscreen source A (facing side-by-side)
68 fs-sbs.a = *
69 fs-sbs.b = 1.0/0.5 0.0
70 fs-sbs.alpha-b = 0
71 fs-sbs.inter = true
72
73 ; fullscreen source A (facing side-by-side-preview)
74 fs-lec.a = *
75 fs-lec.b = 1.0 0.0
76 fs-lec.alpha-b = 0
77 fs-lec.crop-b = 0.31/0
78 fs-lec.inter = true
79 fs-lec.mirror = true
80
81 ; picture-in-picture (fullscreen source A with B as small overlay)
82 pip.a = *
83 pip.b = 0.73/0.72 0.26
84 pip.noswap = true
85 pip.mirror = true
86
87 ; side-by-side (source A at left and B at right side)
88 sbs.a = 0.008/0.25 0.49
89 sbs.b = 0.503/0.25 0.49
90
91 ; side-by-side-preview (source A bigger and B smaller and cropped beside)
92 lec.a = 0.006/0.01 0.75
93 lec.b = 0.60/0.42 0.56
94 lec.crop-b = 0.31/0
95 lec.mirror = true
96
97 ; side-by-side-preview (source A bigger and B smaller and cropped beside)
98 lec_43.a = -0.125/0.0 1.0
99 lec_43.b = 0.60/0.42 0.56
100 lec_43.crop-a = 0.125/0
101 lec_43.crop-b = 0.31/0
102 lec_43.mirror = true
103
104 ; fullscreen source B (overlapping A)
105 fs-b.a = *
106 fs-b.b = *
107 fs-b.noswap = true
108
109 ; fullscreen source B (facing side-by-side)
110 fs-b-sbs.a = 0.0/0.5 0.0
111 fs-b-sbs.alpha-a = 0.0
112 fs-b-sbs.b = *
113 fs-b-sbs.inter = true
114
115 ; fullscreen source B (facing side-by-side-preview)
116 fs-b-lec.a = 0.0/1.0 0.0
117 fs-b-lec.b = *
118 fs-b-lec.inter = true
119 fs-b-lec.mirror = true
120
121 ; one-opon-the-other (like one-opon-the-other but overlapping)
122 oao.a = 0.3/0.2 0.4
123 oao.alpha-a = 0.5
124 oao.b = 0.2/0.3 0.6
125 oao.inter = true
126 oao.noswap = true
127
128 [transitions]
129 ; list of transitions each one can be freely named and is a list of composites
130 ; which will be morphed into an animation. Interpolation will be linear with two
131 ; composites and B-Splines for more.
132
133 ; unique name = ms, from / [... /] to
134 fs-fs = 750, fs / fs-b
135 fs-pip = 750, fs-pip / pip
136 fs-sbs = 750, fs-sbs / sbs
137 fs-b-pip = 750, fs-b / pip
138 fs-b-sbs = 750, fs-b-sbs / sbs
139 fs-lec = 750, fs-lec / lec
140 fs-b-lec = 750, fs-b-lec / lec
141 fs-lec_43 = 750, fs-lec / lec_43
142 fs-b-lec_43 = 750, fs-b-lec / lec_43
143 pip-pip = 750, pip / sbs / pip
144 sbs-sbs = 750, sbs / oao / sbs
145 _sbs-sbs = 750, ^sbs / ^oao / sbs
146
147 fs-pip_ = 750, |fs-pip / |pip
148 fs-b-pip_ = 750, fs-b / |pip
149 fs-lec_ = 750, fs-lec / |lec
150 fs-lec_43_ = 750, fs-lec / |lec_43
151 fs-b-lec_ = 750, fs-b-lec / |lec
152 fs-b-lec_43_ = 750, fs-b-lec / |lec_43
153 pip-pip_ = 750, |pip / sbs / |pip
154
155 ; default blending
156 ; unique name = ms, from / [... /] to
157 def = 750, * / *
158
159
160 [toolbar.sources.a]
161 buttons = cam1,cam2,cam3,grabber
162
163 cam1.key = F1
164 cam1.tip = Select source CAM1 on channel A
165
166 cam2.key = F2
167 cam2.tip = Select source CAM2 on channel A
168
169 cam3.key = F3
170 cam3.tip = Select source CAM3 on channel A
171
172 grabber.key = F4
173 grabber.tip = Select source GRABBER on channel A
174
175 [toolbar.sources.b]
176 buttons = cam1,cam2,cam3,grabber
177
178 cam1.key = 1
179 cam1.tip = Select source CAM1 on channel B
180
181 cam2.key = 2
182 cam2.tip = Select source CAM2 on channel B
183
184 cam3.key = 3
185 cam3.tip = Select source CAM3 on channel B
186
187 grabber.key = 4
188 grabber.tip = Select source GRABBER on channel B
189
190 [toolbar.composites]
191 buttons = fs,sbs,lec
192
193 fs.name = FULL SCREEN
194 fs.key = F5
195 fs.tip = Show channel A on full screen
196
197 sbs.name = SIDE BY SIDE
198 sbs.key = F6
199 sbs.tip = Put channel A beside channel B
200
201 lec.name = LECTURE
202 lec.key = F7
203 lec.tip = Put cropped channel B beside large channel A
204
205 [toolbar.mods]
206 buttons = mirror,ratio
207
208 mirror.name = MIRROR
209 mirror.key = F9
210 mirror.replace = lec->|lec
211 mirror.tip = Horizontally mirror composite\n(e.g. when speaker moves to the other side)
212
213 ratio.name = 4:3
214 ratio.replace = lec->lec_43
215 ratio.key = F10
216 ratio.tip = Crop channel A to 4:3 ratio
217
218 [toolbar.mix]
219 buttons = retake,cut,trans
220
221 retake.name = RETAKE
222 retake.key = BackSpace
223 retake.tip = Copy output composite to preview for modification\n(output remains untouched)
224
225 cut.name = CUT
226 cut.key = Return
227 cut.tip = Hard cut preview composite to output.
228 cut.expand = True
229
230 trans.name = TRANS
231 trans.key = space
232 trans.tip = Use transition to cut preview composite to output
233 trans.expand = True
234
235 [toolbar.insert]
236 auto-off.name = AUTO-OFF
237 auto-off.key = o
238 auto-off.tip = automatically turn off insertion before every mix
239
240 update.name = UPDATE
241 update.key = u
242 update.tip = Update current event
243
244 insert.name = INSERT
245 insert.key = i
246 insert.tip = Show or hide current insertion
0 [mix]
1 #videocaps = video/x-raw,format=I420,width=1920,height=1080,framerate=25/1,pixel-aspect-ratio=1/1,interlace-mode=progressive
2 audiocaps = audio/x-raw,format=S16LE,channels=8,layout=interleaved,rate=48000
3
4 ; tcp listening ports will be 16000,16001,...
5 sources = cam1,cam2,cam3,grabber
6 backgrounds = background_sbs, background_lec, background_lecm, background_fs
7
8 [source.cam1]
9 audio.original = 0+1
10
11 [source.cam2]
12 audio.english = 0+1
13 audio.french = 2+3
14
15 [source.grabber]
16 audio.laptop = 0+1
17
18 [previews]
19 enabled = true
20 live = true
21 #vaapi=h264
22 videocaps=video/x-raw,width=1024,height=576,framerate=25/1
23
24 [blinder]
25 enabled = true
26 sources = pause
27
28 [source.blinder]
29 audio.original = 0+1
30 audio.english = 0+1
31 audio.french = 0+1
32 audio.laptop = 0+1
33
34 [source.background_fs]
35 kind=test
36 pattern=black
37 composites=fs
38
39 [source.background_sbs]
40 kind=img
41 file=data/images/36c3_sbs.png
42 composites=sbs
43
44 [source.background_lec]
45 kind=img
46 file=data/images/36c3_lec.png
47 composites=lec,lec_43
48
49 [source.background_lecm]
50 kind=img
51 file=data/images/36c3_lecm.png
52 composites=|lec,|lec_43
53
54 [overlay]
55 ; default selection for overlay image
56 file = watermark.png|Watermark
57 ; user selection of overlay images
58 files = transparency.png|Transparency Test,watermark|Watermark,../voc2bg.png|35c3 Background
59 path = ./data/images/overlays
60
61 ; read user selection from schedule.xml file
62 schedule=schedule.xml
63 ; filter by room
64 room=HALL 1
65 ; filter by event ID (good for testing)
66 ;event=3
67 ; should the user be able to toggle the AUTO-OFF button?
68 user-auto-off = true
69 ; should the AUTO-OFF button be initially be off?
70 ;auto-off = false
71 ; set fading time when showing or hiding overlay
72 ;blend-time=300
73
74 [composites]
75 ; fullscreen source A (B is full transparent)
76 fs.a = *
77 fs.b = *
78 fs.alpha-b = 0
79 fs.noswap = true
80
81 ; fullscreen source A (facing picture-in-picture)
82 fs-pip.a = *
83 fs-pip.b = 0.86/0.85 0.0
84 fs-pip.alpha-b = 0
85 fs-pip.inter = true
86 fs-pip.mirror = true
87
88 ; fullscreen source A (facing side-by-side)
89 fs-sbs.a = *
90 fs-sbs.b = 1.0/0.5 0.0
91 fs-sbs.alpha-b = 0
92 fs-sbs.inter = true
93
94 ; fullscreen source A (facing side-by-side-preview)
95 fs-lec.a = *
96 fs-lec.b = 1.0 0.0
97 fs-lec.alpha-b = 0
98 fs-lec.crop-b = 0.31/0
99 fs-lec.inter = true
100 fs-lec.mirror = true
101
102 ; picture-in-picture (fullscreen source A with B as small overlay)
103 pip.a = *
104 pip.b = 0.73/0.72 0.26
105 pip.noswap = true
106 pip.mirror = true
107
108 ; side-by-side (source A at left and B at right side)
109 sbs.a = 0.008/0.25 0.49
110 sbs.b = 0.503/0.25 0.49
111
112 ; side-by-side-preview (source A bigger and B smaller and cropped beside)
113 lec.a = 0.006/0.01 0.75
114 lec.b = 0.60/0.42 0.56
115 lec.crop-b = 0.31/0
116 lec.mirror = true
117
118 ; side-by-side-preview (source A bigger and B smaller and cropped beside)
119 lec_43.a = -0.125/0.0 1.0
120 lec_43.b = 0.60/0.42 0.56
121 lec_43.crop-a = 0.125/0
122 lec_43.crop-b = 0.31/0
123 lec_43.mirror = true
124
125 ; fullscreen source B (overlapping A)
126 fs-b.a = *
127 fs-b.b = *
128 fs-b.noswap = true
129
130 ; fullscreen source B (facing side-by-side)
131 fs-b-sbs.a = 0.0/0.5 0.0
132 fs-b-sbs.alpha-a = 0.0
133 fs-b-sbs.b = *
134 fs-b-sbs.inter = true
135
136 ; fullscreen source B (facing side-by-side-preview)
137 fs-b-lec.a = 0.0/1.0 0.0
138 fs-b-lec.b = *
139 fs-b-lec.inter = true
140 fs-b-lec.mirror = true
141
142 ; one-opon-the-other (like one-opon-the-other but overlapping)
143 oao.a = 0.3/0.2 0.4
144 oao.alpha-a = 0.5
145 oao.b = 0.2/0.3 0.6
146 oao.inter = true
147 oao.noswap = true
148
149 [transitions]
150 ; list of transitions each one can be freely named and is a list of composites
151 ; which will be morphed into an animation. Interpolation will be linear with two
152 ; composites and B-Splines for more.
153
154 ; unique name = ms, from / [... /] to
155 fs-fs = 750, fs / fs-b
156 fs-pip = 750, fs-pip / pip
157 fs-sbs = 750, fs-sbs / sbs
158 fs-b-pip = 750, fs-b / pip
159 fs-b-sbs = 750, fs-b-sbs / sbs
160 fs-lec = 750, fs-lec / lec
161 fs-b-lec = 750, fs-b-lec / lec
162 fs-lec_43 = 750, fs-lec / lec_43
163 fs-b-lec_43 = 750, fs-b-lec / lec_43
164 pip-pip = 750, pip / sbs / pip
165 sbs-sbs = 750, sbs / oao / sbs
166 _sbs-sbs = 750, ^sbs / ^oao / sbs
167
168 fs-pip_ = 750, |fs-pip / |pip
169 fs-b-pip_ = 750, fs-b / |pip
170 fs-lec_ = 750, fs-lec / |lec
171 fs-lec_43_ = 750, fs-lec / |lec_43
172 fs-b-lec_ = 750, fs-b-lec / |lec
173 fs-b-lec_43_ = 750, fs-b-lec / |lec_43
174 pip-pip_ = 750, |pip / sbs / |pip
175
176 ; default blending
177 ; unique name = ms, from / [... /] to
178 def = 750, * / *
179
180
181 [toolbar.sources.a]
182 buttons = cam1,cam2,cam3,grabber
183
184 cam1.key = F1
185 cam1.tip = Select source CAM1 on channel A
186
187 cam2.key = F2
188 cam2.tip = Select source CAM2 on channel A
189
190 cam3.key = F3
191 cam3.tip = Select source CAM3 on channel A
192
193 grabber.key = F4
194 grabber.tip = Select source GRABBER on channel A
195
196 [toolbar.sources.b]
197 buttons = cam1,cam2,cam3,grabber
198
199 cam1.key = 1
200 cam1.tip = Select source CAM1 on channel B
201
202 cam2.key = 2
203 cam2.tip = Select source CAM2 on channel B
204
205 cam3.key = 3
206 cam3.tip = Select source CAM3 on channel B
207
208 grabber.key = 4
209 grabber.tip = Select source GRABBER on channel B
210
211 [toolbar.composites]
212 buttons = fs,sbs,lec
213
214 fs.name = FULL SCREEN
215 fs.key = F5
216 fs.tip = Show channel A on full screen
217
218 sbs.name = SIDE BY SIDE
219 sbs.key = F6
220 sbs.tip = Put channel A beside channel B
221
222 lec.name = LECTURE
223 lec.key = F7
224 lec.tip = Put cropped channel B beside large channel A
225
226 [toolbar.mods]
227 buttons = mirror,ratio
228
229 mirror.name = MIRROR
230 mirror.key = F9
231 mirror.replace = lec->|lec
232 mirror.tip = Horizontally mirror composite\n(e.g. when speaker moves to the other side)
233
234 ratio.name = 4:3
235 ratio.replace = lec->lec_43
236 ratio.key = F10
237 ratio.tip = Crop channel A to 4:3 ratio
238
239 [toolbar.mix]
240 buttons = retake,cut,trans
241
242 retake.name = RETAKE
243 retake.key = BackSpace
244 retake.tip = Copy output composite to preview for modification\n(output remains untouched)
245
246 cut.name = CUT
247 cut.key = Return
248 cut.tip = Hard cut preview composite to output.
249 cut.expand = True
250
251 trans.name = TRANS
252 trans.key = space
253 trans.tip = Use transition to cut preview composite to output
254 trans.expand = True
255
256 [toolbar.insert]
257 auto-off.name = AUTO-OFF
258 auto-off.key = o
259 auto-off.tip = "automatically turn off insertion before every mix"
260
261 update.name = UPDATE
262 update.key = u
263 update.tip = Update current event
264
265 insert.name = INSERT
266 insert.key = i
267 insert.tip = Show or hide current insertion
0 [mix]
1 sources = CAM1,CAM2,LAPTOP
2
3 [previews]
4 ; enable previews so we can see something in VOC2GUI
5 enabled = true
6 ; enable live preview so we can see the blinder working
7 live = true
8
9 [overlay]
10 ; path for all image files
11 path = ./data/images/overlays
12
13 ; filter by event ID (good for testing)
14 ;event=3
15
16 ; should the user be able to toggle the AUTO-OFF button?
17 user-auto-off = true
18
19 ; should the AUTO-OFF button be initially be off?
20 ;auto-off = false
21
22 ; set fading time when showing or hiding overlay
23 ;blend-time=300
24
25 ; default selection for overlay image
26 file = watermark.png|Watermark
27
28 ; user selection of overlay images
29 files = transparency.png|Transparency Test,watermark|Watermark,../voc2bg.png|35c3 Background
30
31 ; read user selection from schedule.xml file
32 schedule=schedule.xml
33
34 ; filter by room
35 room=HALL 1
36
37 [composites]
38 ; fullscreen source B is full transparent
39 FULL.alpha-b = 0
40
41 [transitions]
42 ; unique name = ms, from / [... /] to
43 FADE = 750, FULL / FULL
0 [mix]
1 sources = CAM1,CAM2
2
3 [source.background]
4 kind=img
5 file=data/images/bg.png
6
7 [previews]
8 ; enable previews so we can see something in VOC2GUI
9 enabled = true
10
11 [composites]
12 ; fullscreen source A (B is full transparent)
13 fs.a = *
14 fs.b = *
15 fs.alpha-b = 0
16 fs.noswap = true
17
18 ; fullscreen source A (facing side-by-side)
19 fs-sbs.a = *
20 fs-sbs.b = 1.0/0.5 0.0
21 fs-sbs.alpha-b = 0
22 fs-sbs.inter = true
23
24 ; side-by-side (source A at left and B at right side)
25 sbs.a = 0.008/0.25 0.49
26 sbs.b = 0.503/0.25 0.49
27
28 ; fullscreen source B (overlapping A)
29 fs-b.a = *
30 fs-b.b = *
31 fs-b.noswap = true
32
33 ; fullscreen source B (facing side-by-side)
34 fs-b-sbs.a = 0.0/0.5 0.0
35 fs-b-sbs.alpha-a = 0.0
36 fs-b-sbs.b = *
37 fs-b-sbs.inter = true
38
39 ; one-opon-the-other (like one-opon-the-other but overlapping)
40 oao.a = 0.3/0.2 0.4
41 oao.alpha-a = 0.5
42 oao.b = 0.2/0.3 0.6
43 oao.inter = true
44 oao.noswap = true
45
46 [transitions]
47 ; fade from fullscreen(A) to fullscreen(B)
48 fs-fs = 750, fs / fs-b
49 ; animate from fullscreen(A) to side-by-side(A,B)
50 fs-sbs = 750, fs-sbs / sbs
51 ; animate from fullscreen(B) to side-by-side(A,B)
52 fs-b-sbs = 750, fs-b-sbs / sbs
53 ; animate from side-by-side(A,B) to side-by-side(B,A)
54 sbs-sbs = 750, sbs / oao / sbs
55
56 [toolbar.composites]
57 buttons = fs,sbs
58
59 fs.name = FULL SCREEN
60 fs.key = F5
61 fs.tip = Show channel A on full screen
62
63 sbs.name = SIDE BY SIDE
64 sbs.key = F6
65 sbs.tip = Put channel A beside channel B
0 [mix]
1 sources = CAM1,CAM2
2
3 [source.background]
4 kind=img
5 file=data/images/bg.png
6
7 [previews]
8 ; enable previews so we can see something in VOC2GUI
9 enabled = true
10
11 [composites]
12 ; side-by-side (source A at left and B at right side)
13 sbs.a = 0.008/0.25 0.49
14 sbs.b = 0.503/0.25 0.49
15
16 [transitions]
17 ; animate from side-by-side(A,B) to side-by-side(B,A)
18 sbs-sbs = 750, sbs / sbs
19
20 [toolbar.composites]
21 buttons = sbs
22
23 sbs.name = SIDE BY SIDE
24 sbs.key = F6
25 sbs.tip = Put channel A beside channel B
0 [mix]
1 sources = CAM1,CAM2,LAPTOP
2
3 [source.CAM1]
4 kind=v4l2
5 device=/dev/video2
6 width=1280
7 height=720
8 framerate=10/1
9 format=YUY2
10
11 [previews]
12 ; enable previews so we can see something in VOC2GUI
13 enabled = false
14 ; enable live preview so we can see the blinder working
15 live = true
16
17 [composites]
18 ; fullscreen source B is full transparent
19 FULL.alpha-b = 0
20
21 [transitions]
22 ; unique name = ms, from / [... /] to
23 FADE = 750, FULL / FULL
0 [mix]
1 sources = CAM1,CAM2,LAPTOP
2
3 [previews]
4 ; enable previews so we can see something in VOC2GUI
5 enabled = true
6 ; enable live preview so we can see the blinder working
7 live = true
8 ; select H264 by vaapi for preview enconding
9 vaapi=h264
10
11 [composites]
12 ; fullscreen source B is full transparent
13 FULL.alpha-b = 0
14
15 [transitions]
16 ; unique name = ms, from / [... /] to
17 FADE = 750, FULL / FULL
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
0 #!/bin/sh
1
2
3 TMP_DIR=$(mktemp -d)
4 PNG_DIR=$PWD/doc/pipelines
5
6 echo Generating temporary DOT files into \'$TMP_DIR\'
7 export GST_DEBUG_DUMP_DOT_DIR=$TMP_DIR
8
9 echo Starting voctocore...
10 timeout 25s ./voctocore/voctocore.py -v -dg &
11 echo Waiting 15 seconds...
12 sleep 15
13 echo Starting voctogui...
14 echo Waiting 5 seconds...
15 timeout 5s ./voctogui/voctogui.py -vv -d
16
17 cd $TMP_DIR
18 echo converting DOT to PNG into \'$PNG_DIR\'...
19 ls
20 for j in *.dot; do dot -Tpng -o${PNG_DIR}/${j%}.png ${j}; done
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
2 <!--Created by yEd 3.18.2-->
3 <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
4 <key for="port" id="d1" yfiles.type="portgraphics"/>
5 <key for="port" id="d2" yfiles.type="portgeometry"/>
6 <key for="port" id="d3" yfiles.type="portuserdata"/>
7 <key attr.name="url" attr.type="string" for="node" id="d4"/>
8 <key attr.name="description" attr.type="string" for="node" id="d5"/>
9 <key for="node" id="d6" yfiles.type="nodegraphics"/>
10 <key for="graphml" id="d7" yfiles.type="resources"/>
11 <key attr.name="url" attr.type="string" for="edge" id="d8"/>
12 <key attr.name="description" attr.type="string" for="edge" id="d9"/>
13 <key for="edge" id="d10" yfiles.type="edgegraphics"/>
14 <graph edgedefault="directed" id="G">
15 <data key="d0" xml:space="preserve"/>
16 <node id="n0" yfiles.foldertype="group">
17 <data key="d4" xml:space="preserve"/>
18 <data key="d6">
19 <y:ProxyAutoBoundsNode>
20 <y:Realizers active="0">
21 <y:GroupNode>
22 <y:Geometry height="734.3044075000003" width="112.0" x="1172.1777123342847" y="169.8099424999998"/>
23 <y:Fill color="#F5F5F5" transparent="false"/>
24 <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
25 <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="112.0" x="0.0" xml:space="preserve" y="0.0">voctogui</y:NodeLabel>
26 <y:Shape type="roundrectangle"/>
27 <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
28 <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
29 <y:BorderInsets bottom="94" bottomF="93.75514999999996" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
30 </y:GroupNode>
31 <y:GroupNode>
32 <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
33 <y:Fill color="#F5F5F5" transparent="false"/>
34 <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
35 <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 2</y:NodeLabel>
36 <y:Shape type="roundrectangle"/>
37 <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
38 <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
39 <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
40 </y:GroupNode>
41 </y:Realizers>
42 </y:ProxyAutoBoundsNode>
43 </data>
44 <graph edgedefault="directed" id="n0:">
45 <node id="n0::n0">
46 <data key="d6">
47 <y:ShapeNode>
48 <y:Geometry height="45.0" width="82.0" x="1187.1777123342847" y="750.3592000000001"/>
49 <y:Fill color="#FFCC00" transparent="false"/>
50 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
51 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.599609375" x="15.7001953125" xml:space="preserve" y="6.53125">Source
52 View<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
53 <y:Shape type="roundrectangle"/>
54 </y:ShapeNode>
55 </data>
56 </node>
57 <node id="n0::n1">
58 <data key="d6">
59 <y:ShapeNode>
60 <y:Geometry height="44.99999999999994" width="82.0" x="1187.1777123342847" y="206.2708799999998"/>
61 <y:Fill color="#FFCC00" transparent="false"/>
62 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
63 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="16.322265625" x="32.8388671875" xml:space="preserve" y="13.515624999999972">UI<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
64 <y:Shape type="roundrectangle"/>
65 </y:ShapeNode>
66 </data>
67 </node>
68 <node id="n0::n2">
69 <data key="d6">
70 <y:ShapeNode>
71 <y:Geometry height="45.0" width="82.0" x="1187.1777123342847" y="591.7864"/>
72 <y:Fill color="#FFCC00" transparent="false"/>
73 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
74 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.740234375" x="24.6298828125" xml:space="preserve" y="6.53125">Mix
75 View<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
76 <y:Shape type="roundrectangle"/>
77 </y:ShapeNode>
78 </data>
79 </node>
80 </graph>
81 </node>
82 <node id="n1" yfiles.foldertype="group">
83 <data key="d4" xml:space="preserve"/>
84 <data key="d6">
85 <y:ProxyAutoBoundsNode>
86 <y:Realizers active="0">
87 <y:GroupNode>
88 <y:Geometry height="734.3044075000003" width="112.82871093749964" x="1302.513356865535" y="169.80994249999986"/>
89 <y:Fill color="#F5F5F5" transparent="false"/>
90 <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
91 <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="112.82871093749964" x="0.0" xml:space="preserve" y="0.0">Internet</y:NodeLabel>
92 <y:Shape type="roundrectangle"/>
93 <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
94 <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
95 <y:BorderInsets bottom="373" bottomF="372.75275000000033" left="1" leftF="0.8287109374996362" right="0" rightF="0.0" top="145" topF="145.4060799999997"/>
96 </y:GroupNode>
97 <y:GroupNode>
98 <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
99 <y:Fill color="#F5F5F5" transparent="false"/>
100 <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
101 <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 3</y:NodeLabel>
102 <y:Shape type="roundrectangle"/>
103 <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
104 <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
105 <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
106 </y:GroupNode>
107 </y:Realizers>
108 </y:ProxyAutoBoundsNode>
109 </data>
110 <graph edgedefault="directed" id="n1:">
111 <node id="n1::n0">
112 <data key="d6">
113 <y:ShapeNode>
114 <y:Geometry height="45.0" width="82.0" x="1318.3420678030345" y="471.3615999999998"/>
115 <y:Fill color="#FFCC00" transparent="false"/>
116 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
117 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="66.23828125" x="7.880859375" xml:space="preserve" y="6.53125">Mix
118 Streaming<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
119 <y:Shape type="roundrectangle"/>
120 </y:ShapeNode>
121 </data>
122 </node>
123 <node id="n1::n1">
124 <data key="d6">
125 <y:ShapeNode>
126 <y:Geometry height="45.0" width="82.0" x="1318.3420678030345" y="351.67695999999955"/>
127 <y:Fill color="#FFCC00" transparent="false"/>
128 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
129 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="74.283203125" x="3.8583984375" xml:space="preserve" y="6.53125">Source
130 Streaming<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
131 <y:Shape type="roundrectangle"/>
132 </y:ShapeNode>
133 </data>
134 </node>
135 </graph>
136 </node>
137 <node id="n2" yfiles.foldertype="group">
138 <data key="d4" xml:space="preserve"/>
139 <data key="d6">
140 <y:ProxyAutoBoundsNode>
141 <y:Realizers active="0">
142 <y:GroupNode>
143 <y:Geometry height="734.3044075000003" width="831.3008518801134" x="325.87686045417126" y="169.8099424999998"/>
144 <y:Fill color="#F5F5F5" transparent="false"/>
145 <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
146 <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="831.3008518801134" x="0.0" xml:space="preserve" y="0.0">voctocore</y:NodeLabel>
147 <y:Shape type="roundrectangle"/>
148 <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
149 <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
150 <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="7" topF="7.499999999999972"/>
151 </y:GroupNode>
152 <y:GroupNode>
153 <y:Geometry height="50.0" width="50.0" x="286.3802723402389" y="38.88953124999986"/>
154 <y:Fill color="#F5F5F5" transparent="false"/>
155 <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
156 <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 4</y:NodeLabel>
157 <y:Shape type="roundrectangle"/>
158 <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
159 <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
160 <y:BorderInsets bottom="19" bottomF="18.5390625" left="0" leftF="0.0" right="40" rightF="40.0" top="0" topF="0.0"/>
161 </y:GroupNode>
162 </y:Realizers>
163 </y:ProxyAutoBoundsNode>
164 </data>
165 <graph edgedefault="directed" id="n2:">
166 <node id="n2::n0">
167 <data key="d6">
168 <y:ShapeNode>
169 <y:Geometry height="30.0" width="30.0" x="1072.7831810842847" y="213.77087999999978"/>
170 <y:Fill color="#FFCC00" transparent="false"/>
171 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
172 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.67578125" x="-17.837890625" xml:space="preserve" y="33.99999999999997">Command<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="3.9999999999999716" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
173 <y:Shape type="ellipse"/>
174 </y:ShapeNode>
175 </data>
176 </node>
177 <node id="n2::n1">
178 <data key="d6">
179 <y:ShapeNode>
180 <y:Geometry height="50.000000000000114" width="88.67415730337052" x="523.5502381056885" y="659.5219199999999"/>
181 <y:Fill color="#CCFFFF" transparent="false"/>
182 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
183 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.935546875" x="7.86930521418526" xml:space="preserve" y="9.03125">Recording
184 Compositor<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
185 <y:Shape type="rectangle"/>
186 </y:ShapeNode>
187 </data>
188 </node>
189 <node id="n2::n2">
190 <data key="d6">
191 <y:ShapeNode>
192 <y:Geometry height="50.00000000000006" width="88.67415730337052" x="733.5502381056885" y="419.4124799999996"/>
193 <y:Fill color="#CCFFFF" transparent="false"/>
194 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
195 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.935546875" x="7.86930521418526" xml:space="preserve" y="9.03125">Live
196 Compositor<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
197 <y:Shape type="rectangle"/>
198 </y:ShapeNode>
199 </data>
200 </node>
201 <node id="n2::n3">
202 <data key="d6">
203 <y:ShapeNode>
204 <y:Geometry height="30.0" width="30.0" x="372.65420420417126" y="429.4124799999996"/>
205 <y:Fill color="#00CCFF" transparent="false"/>
206 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
207 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.33984375" x="-17.169921875" xml:space="preserve" y="34.0">SB Video<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
208 <y:Shape type="ellipse"/>
209 </y:ShapeNode>
210 </data>
211 </node>
212 <node id="n2::n4">
213 <data key="d6">
214 <y:ShapeNode>
215 <y:Geometry height="30.0" width="30.0" x="372.65420420417126" y="669.5219199999999"/>
216 <y:Fill color="#00CCFF" transparent="false"/>
217 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
218 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="93.150390625" x="-31.5751953125" xml:space="preserve" y="34.0">Video Source<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
219 <y:Shape type="ellipse"/>
220 </y:ShapeNode>
221 </data>
222 </node>
223 <node id="n2::n5">
224 <data key="d6">
225 <y:ShapeNode>
226 <y:Geometry height="30.0" width="30.0" x="1072.7831810842847" y="478.8615999999998"/>
227 <y:Fill color="#99CC00" transparent="false"/>
228 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
229 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="53.107421875" x="-11.5537109375" xml:space="preserve" y="34.0">Mix Live<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
230 <y:Shape type="ellipse"/>
231 </y:ShapeNode>
232 </data>
233 </node>
234 <node id="n2::n6">
235 <data key="d6">
236 <y:ShapeNode>
237 <y:Geometry height="30.0" width="30.0" x="1072.7831810842847" y="359.17695999999955"/>
238 <y:Fill color="#99CC00" transparent="false"/>
239 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
240 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="73.796875" x="-21.8984375" xml:space="preserve" y="34.0">Source Live<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
241 <y:Shape type="ellipse"/>
242 </y:ShapeNode>
243 </data>
244 </node>
245 <node id="n2::n7">
246 <data key="d6">
247 <y:ShapeNode>
248 <y:Geometry height="30.0" width="30.0" x="1072.7831810842847" y="757.8592000000001"/>
249 <y:Fill color="#99CC00" transparent="false"/>
250 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
251 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="108.7890625" x="-39.39453125" xml:space="preserve" y="34.0">Source Preview<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
252 <y:Shape type="ellipse"/>
253 </y:ShapeNode>
254 </data>
255 </node>
256 <node id="n2::n8">
257 <data key="d6">
258 <y:ShapeNode>
259 <y:Geometry height="30.0" width="88.67415730337052" x="581.107113449103" y="777.1456000000001"/>
260 <y:Fill color="#CCFFFF" transparent="false"/>
261 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
262 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.984375" x="16.34489115168526" xml:space="preserve" y="6.015625">Rescale<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
263 <y:Shape type="rectangle"/>
264 </y:ShapeNode>
265 </data>
266 </node>
267 <node id="n2::n9">
268 <data key="d6">
269 <y:ShapeNode>
270 <y:Geometry height="30.0" width="30.0" x="372.65420420417126" y="740.7219200000002"/>
271 <y:Fill color="#FF6600" transparent="false"/>
272 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
273 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="93.5546875" x="-31.77734375" xml:space="preserve" y="34.0">Audio Source<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
274 <y:Shape type="ellipse"/>
275 </y:ShapeNode>
276 </data>
277 </node>
278 <node id="n2::n10">
279 <data key="d6">
280 <y:ShapeNode>
281 <y:Geometry height="148.89824000000036" width="49.5" x="974.6793845604084" y="419.4124799999996"/>
282 <y:Fill color="#99CC00" transparent="false"/>
283 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
284 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="10.2197265625" xml:space="preserve" y="65.46474500000016">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
285 <y:Shape type="rectangle"/>
286 </y:ShapeNode>
287 </data>
288 </node>
289 <node id="n2::n11">
290 <data key="d6">
291 <y:ShapeNode>
292 <y:Geometry height="68.57280000000003" width="50.60827334971941" x="974.6793845604084" y="659.2864"/>
293 <y:Fill color="#99CC00" transparent="false"/>
294 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
295 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="10.773863237359706" xml:space="preserve" y="25.30202500000007">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
296 <y:Shape type="rectangle"/>
297 </y:ShapeNode>
298 </data>
299 </node>
300 <node id="n2::n12">
301 <data key="d6">
302 <y:ShapeNode>
303 <y:Geometry height="30.0" width="30.0" x="372.65420420417126" y="496.38703999999973"/>
304 <y:Fill color="#FF6600" transparent="false"/>
305 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
306 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.744140625" x="-17.3720703125" xml:space="preserve" y="34.0">SB Audio<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
307 <y:Shape type="ellipse"/>
308 </y:ShapeNode>
309 </data>
310 </node>
311 <node id="n2::n13">
312 <data key="d6">
313 <y:ShapeNode>
314 <y:Geometry height="50.00000000000006" width="88.67415730337052" x="654.1696409761237" y="574.9999999999998"/>
315 <y:Fill color="#FFCC99" transparent="false"/>
316 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
317 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.548828125" x="8.06266458918526" xml:space="preserve" y="9.03125">Recording
318 Audiomixer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
319 <y:Shape type="rectangle"/>
320 </y:ShapeNode>
321 </data>
322 </node>
323 <node id="n2::n14">
324 <data key="d6">
325 <y:ShapeNode>
326 <y:Geometry height="30.0" width="30.0" x="1072.7831810842847" y="678.5727999999999"/>
327 <y:Fill color="#99CC00" transparent="false"/>
328 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
329 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="70.287109375" x="-20.1435546875" xml:space="preserve" y="34.0">Mix Output<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
330 <y:Shape type="ellipse"/>
331 </y:ShapeNode>
332 </data>
333 </node>
334 <node id="n2::n15">
335 <data key="d6">
336 <y:ShapeNode>
337 <y:Geometry height="68.57280000000003" width="50.60827334971941" x="974.6793845604084" y="817.8592000000001"/>
338 <y:Fill color="#99CC00" transparent="false"/>
339 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
340 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="10.773863237359706" xml:space="preserve" y="25.302024999999958">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
341 <y:Shape type="rectangle"/>
342 </y:ShapeNode>
343 </data>
344 </node>
345 <node id="n2::n16">
346 <data key="d6">
347 <y:ShapeNode>
348 <y:Geometry height="11.898239999999987" width="11.898239999999987" x="475.92488089590057" y="678.5727999999999"/>
349 <y:Fill color="#00CCFF" transparent="false"/>
350 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
351 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="3.9491199999999935" y="3.9491199999999935">
352 <y:LabelModel>
353 <y:SmartNodeLabelModel distance="4.0"/>
354 </y:LabelModel>
355 <y:ModelParameter>
356 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
357 </y:ModelParameter>
358 </y:NodeLabel>
359 <y:Shape type="ellipse"/>
360 </y:ShapeNode>
361 </data>
362 </node>
363 <node id="n2::n17">
364 <data key="d6">
365 <y:ShapeNode>
366 <y:Geometry height="30.0" width="30.0" x="1072.7831810842847" y="599.2864"/>
367 <y:Fill color="#99CC00" transparent="false"/>
368 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
369 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.7890625" x="-22.89453125" xml:space="preserve" y="34.0">Mix Preview<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
370 <y:Shape type="ellipse"/>
371 </y:ShapeNode>
372 </data>
373 </node>
374 <node id="n2::n18">
375 <data key="d6">
376 <y:ShapeNode>
377 <y:Geometry height="30.0" width="88.67415730337052" x="813.8703760934063" y="618.5727999999999"/>
378 <y:Fill color="#CCFFFF" transparent="false"/>
379 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
380 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.640625" x="19.01676615168526" xml:space="preserve" y="6.015625">Rescale<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
381 <y:Shape type="rectangle"/>
382 </y:ShapeNode>
383 </data>
384 </node>
385 <node id="n2::n19">
386 <data key="d6">
387 <y:ShapeNode>
388 <y:Geometry height="68.57280000000003" width="50.60827334971941" x="974.6793845604084" y="339.8905599999995"/>
389 <y:Fill color="#99CC00" transparent="false"/>
390 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
391 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.224609375" x="9.191831987359706" xml:space="preserve" y="25.302025000000015">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
392 <y:Shape type="rectangle"/>
393 </y:ShapeNode>
394 </data>
395 </node>
396 <node id="n2::n20">
397 <data key="d6">
398 <y:ShapeNode>
399 <y:Geometry height="29.999999999999943" width="576.1805444610247" x="449.10711344910305" y="213.77087999999983"/>
400 <y:Fill color="#FFCC00" transparent="false"/>
401 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
402 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="98.4765625" x="238.85199098051237" xml:space="preserve" y="6.015624999999972">Command Loop<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
403 <y:Shape type="rectangle"/>
404 </y:ShapeNode>
405 </data>
406 </node>
407 <node id="n2::n21">
408 <data key="d6">
409 <y:ShapeNode>
410 <y:Geometry height="30.0" width="30.0" x="1074.4293845604084" y="837.1456000000001"/>
411 <y:Fill color="#99CC00" transparent="false"/>
412 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
413 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="90.9765625" x="-30.48828125" xml:space="preserve" y="34.0">Source Output<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="-0.5" nodeRatioX="0.0" nodeRatioY="0.5" offsetX="0.0" offsetY="4.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
414 <y:Shape type="ellipse"/>
415 </y:ShapeNode>
416 </data>
417 </node>
418 <node id="n2::n22">
419 <data key="d6">
420 <y:ShapeNode>
421 <y:Geometry height="68.57280000000003" width="50.60827334971941" x="974.6793845604084" y="738.5727999999999"/>
422 <y:Fill color="#99CC00" transparent="false"/>
423 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
424 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.224609375" x="9.191831987359706" xml:space="preserve" y="25.302024999999958">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
425 <y:Shape type="rectangle"/>
426 </y:ShapeNode>
427 </data>
428 </node>
429 <node id="n2::n23">
430 <data key="d6">
431 <y:ShapeNode>
432 <y:Geometry height="68.57280000000003" width="49.5" x="974.6793845604084" y="580.0"/>
433 <y:Fill color="#99CC00" transparent="false"/>
434 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
435 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="10.2197265625" xml:space="preserve" y="25.30202500000007">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
436 <y:Shape type="rectangle"/>
437 </y:ShapeNode>
438 </data>
439 </node>
440 <node id="n2::n24">
441 <data key="d6">
442 <y:ShapeNode>
443 <y:Geometry height="11.898239999999987" width="11.898239999999987" x="692.5575996278089" y="749.7728000000002"/>
444 <y:Fill color="#FF6600" transparent="false"/>
445 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
446 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="3.9491199999999935" y="3.9491199999999935">
447 <y:LabelModel>
448 <y:SmartNodeLabelModel distance="4.0"/>
449 </y:LabelModel>
450 <y:ModelParameter>
451 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
452 </y:ModelParameter>
453 </y:NodeLabel>
454 <y:Shape type="ellipse"/>
455 </y:ShapeNode>
456 </data>
457 </node>
458 <node id="n2::n25">
459 <data key="d6">
460 <y:ShapeNode>
461 <y:Geometry height="11.898239999999987" width="11.898239999999987" x="777.1713093105764" y="594.05088"/>
462 <y:Fill color="#FF6600" transparent="false"/>
463 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
464 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="3.9491199999999935" y="3.9491199999999935">
465 <y:LabelModel>
466 <y:SmartNodeLabelModel distance="4.0"/>
467 </y:LabelModel>
468 <y:ModelParameter>
469 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
470 </y:ModelParameter>
471 </y:NodeLabel>
472 <y:Shape type="ellipse"/>
473 </y:ShapeNode>
474 </data>
475 </node>
476 <node id="n2::n26">
477 <data key="d6">
478 <y:ShapeNode>
479 <y:Geometry height="74.94912000000008" width="88.67415730337052" x="813.8703760934063" y="493.36159999999984"/>
480 <y:Fill color="#FFCC99" transparent="false"/>
481 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
482 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.548828125" x="8.06266458918526" xml:space="preserve" y="21.505810000000054">Live
483 Audiomixer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
484 <y:Shape type="rectangle"/>
485 </y:ShapeNode>
486 </data>
487 </node>
488 <node id="n2::n27">
489 <data key="d6">
490 <y:ShapeNode>
491 <y:Geometry height="11.898239999999987" width="11.898239999999987" x="771.9381967573738" y="308.9017599999996"/>
492 <y:Fill color="#000000" transparent="false"/>
493 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
494 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="3.9491199999999935" y="3.9491199999999935">
495 <y:LabelModel>
496 <y:SmartNodeLabelModel distance="4.0"/>
497 </y:LabelModel>
498 <y:ModelParameter>
499 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
500 </y:ModelParameter>
501 </y:NodeLabel>
502 <y:Shape type="ellipse"/>
503 </y:ShapeNode>
504 </data>
505 </node>
506 <node id="n2::n28">
507 <data key="d6">
508 <y:ShapeNode>
509 <y:Geometry height="11.898239999999987" width="11.898239999999987" x="751.2221893105764" y="678.5727999999999"/>
510 <y:Fill color="#00CCFF" transparent="false"/>
511 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
512 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="3.9491199999999935" y="3.9491199999999935">
513 <y:LabelModel>
514 <y:SmartNodeLabelModel distance="4.0"/>
515 </y:LabelModel>
516 <y:ModelParameter>
517 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
518 </y:ModelParameter>
519 </y:NodeLabel>
520 <y:Shape type="ellipse"/>
521 </y:ShapeNode>
522 </data>
523 </node>
524 <node id="n2::n29">
525 <data key="d6">
526 <y:ShapeNode>
527 <y:Geometry height="68.57280000000003" width="88.67415730337052" x="583.5502381056885" y="339.8905599999996"/>
528 <y:Fill color="#CCFFFF" transparent="false"/>
529 <y:BorderStyle color="#000000" raised="false" type="line" width="3.0"/>
530 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="81.54296875" x="3.5655942766852604" xml:space="preserve" y="18.317650000000015">Source
531 Compositor<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
532 <y:Shape type="rectangle"/>
533 </y:ShapeNode>
534 </data>
535 </node>
536 <node id="n2::n30">
537 <data key="d6">
538 <y:ShapeNode>
539 <y:Geometry height="11.898239999999987" width="11.898239999999987" x="441.7050842041713" y="438.4633599999996"/>
540 <y:Fill color="#00CCFF" transparent="false"/>
541 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
542 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="3.9491199999999935" y="3.9491199999999935">
543 <y:LabelModel>
544 <y:SmartNodeLabelModel distance="4.0"/>
545 </y:LabelModel>
546 <y:ModelParameter>
547 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
548 </y:ModelParameter>
549 </y:NodeLabel>
550 <y:Shape type="ellipse"/>
551 </y:ShapeNode>
552 </data>
553 </node>
554 <node id="n2::n31">
555 <data key="d6">
556 <y:ShapeNode>
557 <y:Geometry height="11.898239999999987" width="11.898239999999987" x="932.7811445604084" y="524.8870399999998"/>
558 <y:Fill color="#FF6600" transparent="false"/>
559 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
560 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="3.9491199999999935" y="3.9491199999999935">
561 <y:LabelModel>
562 <y:SmartNodeLabelModel distance="4.0"/>
563 </y:LabelModel>
564 <y:ModelParameter>
565 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
566 </y:ModelParameter>
567 </y:NodeLabel>
568 <y:Shape type="ellipse"/>
569 </y:ShapeNode>
570 </data>
571 </node>
572 </graph>
573 </node>
574 <node id="n3" yfiles.foldertype="group">
575 <data key="d4" xml:space="preserve"/>
576 <data key="d6">
577 <y:ProxyAutoBoundsNode>
578 <y:Realizers active="0">
579 <y:GroupNode>
580 <y:Geometry height="734.3044075000003" width="112.0" x="1434.5064232717843" y="169.8099424999998"/>
581 <y:Fill color="#F5F5F5" transparent="false"/>
582 <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
583 <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="112.0" x="0.0" xml:space="preserve" y="0.0">Encoder</y:NodeLabel>
584 <y:Shape type="roundrectangle"/>
585 <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
586 <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
587 <y:BorderInsets bottom="14" bottomF="14.46875" left="0" leftF="0.0" right="0" rightF="0.0" top="465" topF="464.8019200000001"/>
588 </y:GroupNode>
589 <y:GroupNode>
590 <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
591 <y:Fill color="#F5F5F5" transparent="false"/>
592 <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
593 <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.4609375" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="65.201171875" x="-7.6005859375" xml:space="preserve" y="0.0">Folder 8</y:NodeLabel>
594 <y:Shape type="roundrectangle"/>
595 <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
596 <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
597 <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
598 </y:GroupNode>
599 </y:Realizers>
600 </y:ProxyAutoBoundsNode>
601 </data>
602 <graph edgedefault="directed" id="n3:">
603 <node id="n3::n0">
604 <data key="d6">
605 <y:ShapeNode>
606 <y:Geometry height="45.0" width="82.0" x="1449.5064232717843" y="829.6456000000001"/>
607 <y:Fill color="#FFCC00" transparent="false"/>
608 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
609 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.767578125" x="8.6162109375" xml:space="preserve" y="6.53125">Source
610 Recording<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
611 <y:Shape type="roundrectangle"/>
612 </y:ShapeNode>
613 </data>
614 </node>
615 <node id="n3::n1">
616 <data key="d6">
617 <y:ShapeNode>
618 <y:Geometry height="45.0" width="82.0" x="1449.5064232717843" y="671.0727999999999"/>
619 <y:Fill color="#FFCC00" transparent="false"/>
620 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
621 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.767578125" x="8.6162109375" xml:space="preserve" y="6.53125">Mix
622 Recording<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
623 <y:Shape type="roundrectangle"/>
624 </y:ShapeNode>
625 </data>
626 </node>
627 </graph>
628 </node>
629 <edge id="e0" source="n2::n21" target="n3::n0">
630 <data key="d10">
631 <y:PolyLineEdge>
632 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
633 <y:LineStyle color="#000000" type="line" width="1.0"/>
634 <y:Arrows source="none" target="standard"/>
635 <y:BendStyle smoothed="false"/>
636 </y:PolyLineEdge>
637 </data>
638 </edge>
639 <edge id="e1" source="n2::n17" target="n0::n2">
640 <data key="d10">
641 <y:PolyLineEdge>
642 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
643 <y:LineStyle color="#000000" type="line" width="1.0"/>
644 <y:Arrows source="none" target="standard"/>
645 <y:BendStyle smoothed="false"/>
646 </y:PolyLineEdge>
647 </data>
648 </edge>
649 <edge id="e2" source="n2::n14" target="n3::n1">
650 <data key="d10">
651 <y:PolyLineEdge>
652 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
653 <y:LineStyle color="#000000" type="line" width="1.0"/>
654 <y:Arrows source="none" target="standard"/>
655 <y:BendStyle smoothed="false"/>
656 </y:PolyLineEdge>
657 </data>
658 </edge>
659 <edge id="e3" source="n2::n5" target="n1::n0">
660 <data key="d10">
661 <y:PolyLineEdge>
662 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
663 <y:LineStyle color="#000000" type="line" width="1.0"/>
664 <y:Arrows source="none" target="standard"/>
665 <y:BendStyle smoothed="false"/>
666 </y:PolyLineEdge>
667 </data>
668 </edge>
669 <edge id="e4" source="n2::n7" target="n0::n0">
670 <data key="d10">
671 <y:PolyLineEdge>
672 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
673 <y:LineStyle color="#000000" type="line" width="4.0"/>
674 <y:Arrows source="none" target="standard"/>
675 <y:BendStyle smoothed="false"/>
676 </y:PolyLineEdge>
677 </data>
678 </edge>
679 <edge id="e5" source="n2::n6" target="n1::n1">
680 <data key="d10">
681 <y:PolyLineEdge>
682 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
683 <y:LineStyle color="#000000" type="line" width="4.0"/>
684 <y:Arrows source="none" target="standard"/>
685 <y:BendStyle smoothed="false"/>
686 </y:PolyLineEdge>
687 </data>
688 </edge>
689 <edge id="e6" source="n0::n1" target="n2::n0">
690 <data key="d10">
691 <y:PolyLineEdge>
692 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
693 <y:LineStyle color="#000000" type="line" width="1.0"/>
694 <y:Arrows source="standard" target="standard"/>
695 <y:BendStyle smoothed="false"/>
696 </y:PolyLineEdge>
697 </data>
698 </edge>
699 <edge id="n2::e0" source="n2::n0" target="n2::n20">
700 <data key="d10">
701 <y:PolyLineEdge>
702 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
703 <y:LineStyle color="#000000" type="line" width="1.0"/>
704 <y:Arrows source="standard" target="standard"/>
705 <y:BendStyle smoothed="false"/>
706 </y:PolyLineEdge>
707 </data>
708 </edge>
709 <edge id="n2::e1" source="n2::n28" target="n2::n2">
710 <data key="d10">
711 <y:PolyLineEdge>
712 <y:Path sx="0.0" sy="0.0" tx="-20.716007446797448" ty="-1.6231167372764048"/>
713 <y:LineStyle color="#00CCFF" type="line" width="1.0"/>
714 <y:Arrows source="none" target="standard"/>
715 <y:BendStyle smoothed="false"/>
716 </y:PolyLineEdge>
717 </data>
718 </edge>
719 <edge id="n2::e2" source="n2::n28" target="n2::n11">
720 <data key="d10">
721 <y:PolyLineEdge>
722 <y:Path sx="0.0" sy="0.0" tx="-3.286136674859449" ty="15.867199999999912">
723 <y:Point x="757.1713093105764" y="709.44"/>
724 </y:Path>
725 <y:LineStyle color="#00CCFF" type="line" width="1.0"/>
726 <y:Arrows source="none" target="standard"/>
727 <y:BendStyle smoothed="true"/>
728 </y:PolyLineEdge>
729 </data>
730 </edge>
731 <edge id="n2::e3" source="n2::n28" target="n2::n18">
732 <data key="d10">
733 <y:PolyLineEdge>
734 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
735 <y:Point x="757.1713093105764" y="634.0"/>
736 </y:Path>
737 <y:LineStyle color="#00CCFF" type="line" width="1.0"/>
738 <y:Arrows source="none" target="standard"/>
739 <y:BendStyle smoothed="true"/>
740 </y:PolyLineEdge>
741 </data>
742 </edge>
743 <edge id="n2::e4" source="n2::n2" target="n2::n10">
744 <data key="d10">
745 <y:PolyLineEdge>
746 <y:Path sx="0.0" sy="0.0" tx="-2.3377173390349526" ty="-49.449120000000164"/>
747 <y:LineStyle color="#00CCFF" type="line" width="1.0"/>
748 <y:Arrows source="none" target="standard"/>
749 <y:BendStyle smoothed="false"/>
750 </y:PolyLineEdge>
751 </data>
752 </edge>
753 <edge id="n2::e5" source="n2::n3" target="n2::n30">
754 <data key="d10">
755 <y:PolyLineEdge>
756 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
757 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
758 <y:Arrows source="none" target="standard"/>
759 <y:BendStyle smoothed="false"/>
760 </y:PolyLineEdge>
761 </data>
762 </edge>
763 <edge id="n2::e6" source="n2::n4" target="n2::n16">
764 <data key="d10">
765 <y:PolyLineEdge>
766 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
767 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
768 <y:Arrows source="none" target="standard"/>
769 <y:BendStyle smoothed="false"/>
770 </y:PolyLineEdge>
771 </data>
772 </edge>
773 <edge id="n2::e7" source="n2::n8" target="n2::n22">
774 <data key="d10">
775 <y:PolyLineEdge>
776 <y:Path sx="6.389021828314753" sy="0.0" tx="1.598183141692016" ty="19.28640000000007"/>
777 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
778 <y:Arrows source="none" target="standard"/>
779 <y:BendStyle smoothed="false"/>
780 </y:PolyLineEdge>
781 </data>
782 </edge>
783 <edge id="n2::e8" source="n2::n9" target="n2::n24">
784 <data key="d10">
785 <y:PolyLineEdge>
786 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
787 <y:LineStyle color="#FF6600" type="line" width="4.0"/>
788 <y:Arrows source="none" target="standard"/>
789 <y:BendStyle smoothed="false"/>
790 </y:PolyLineEdge>
791 </data>
792 </edge>
793 <edge id="n2::e9" source="n2::n10" target="n2::n5">
794 <data key="d10">
795 <y:PolyLineEdge>
796 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
797 <y:LineStyle color="#99CC00" type="line" width="1.0"/>
798 <y:Arrows source="none" target="standard"/>
799 <y:BendStyle smoothed="false"/>
800 </y:PolyLineEdge>
801 </data>
802 </edge>
803 <edge id="n2::e10" source="n2::n11" target="n2::n14">
804 <data key="d10">
805 <y:PolyLineEdge>
806 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
807 <y:LineStyle color="#99CC00" type="line" width="1.0"/>
808 <y:Arrows source="none" target="standard"/>
809 <y:BendStyle smoothed="false"/>
810 </y:PolyLineEdge>
811 </data>
812 </edge>
813 <edge id="n2::e11" source="n2::n12" target="n2::n26">
814 <data key="d10">
815 <y:PolyLineEdge>
816 <y:Path sx="0.0" sy="0.0" tx="-4.722590325858391" ty="-19.449120000000164"/>
817 <y:LineStyle color="#FF6600" type="line" width="4.0"/>
818 <y:Arrows source="none" target="standard"/>
819 <y:BendStyle smoothed="false"/>
820 </y:PolyLineEdge>
821 </data>
822 </edge>
823 <edge id="n2::e12" source="n2::n13" target="n2::n25">
824 <data key="d10">
825 <y:PolyLineEdge>
826 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
827 <y:LineStyle color="#FF6600" type="line" width="1.0"/>
828 <y:Arrows source="none" target="standard"/>
829 <y:BendStyle smoothed="true"/>
830 </y:PolyLineEdge>
831 </data>
832 </edge>
833 <edge id="n2::e13" source="n2::n25" target="n2::n26">
834 <data key="d10">
835 <y:PolyLineEdge>
836 <y:Path sx="0.0" sy="0.0" tx="1.020883338141516" ty="16.723803648000114">
837 <y:Point x="783.1204293105764" y="547.5599636479999"/>
838 </y:Path>
839 <y:LineStyle color="#FF6600" type="line" width="1.0"/>
840 <y:Arrows source="none" target="standard"/>
841 <y:BendStyle smoothed="true"/>
842 </y:PolyLineEdge>
843 </data>
844 </edge>
845 <edge id="n2::e14" source="n2::n15" target="n2::n21">
846 <data key="d10">
847 <y:PolyLineEdge>
848 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
849 <y:LineStyle color="#99CC00" type="line" width="1.0"/>
850 <y:Arrows source="none" target="standard"/>
851 <y:BendStyle smoothed="false"/>
852 </y:PolyLineEdge>
853 </data>
854 </edge>
855 <edge id="n2::e15" source="n2::n24" target="n2::n13">
856 <data key="d10">
857 <y:PolyLineEdge>
858 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
859 <y:LineStyle color="#FF6600" type="line" width="4.0"/>
860 <y:Arrows source="none" target="standard"/>
861 <y:BendStyle smoothed="true"/>
862 </y:PolyLineEdge>
863 </data>
864 </edge>
865 <edge id="n2::e16" source="n2::n16" target="n2::n15">
866 <data key="d10">
867 <y:PolyLineEdge>
868 <y:Path sx="0.0" sy="0.0" tx="1.594965138211137" ty="15.858811650470898">
869 <y:Point x="481.87400089590056" y="868.004411650471"/>
870 </y:Path>
871 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
872 <y:Arrows source="none" target="standard"/>
873 <y:BendStyle smoothed="true"/>
874 </y:PolyLineEdge>
875 </data>
876 </edge>
877 <edge id="n2::e17" source="n2::n16" target="n2::n1">
878 <data key="d10">
879 <y:PolyLineEdge>
880 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
881 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
882 <y:Arrows source="none" target="standard"/>
883 <y:BendStyle smoothed="false"/>
884 </y:PolyLineEdge>
885 </data>
886 </edge>
887 <edge id="n2::e18" source="n2::n16" target="n2::n29">
888 <data key="d10">
889 <y:PolyLineEdge>
890 <y:Path sx="0.0" sy="0.0" tx="-30.09772577882831" ty="18.489520000000653">
891 <y:Point x="481.87400089590056" y="392.6664800000002"/>
892 </y:Path>
893 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
894 <y:Arrows source="none" target="standard"/>
895 <y:BendStyle smoothed="true"/>
896 </y:PolyLineEdge>
897 </data>
898 </edge>
899 <edge id="n2::e19" source="n2::n16" target="n2::n8">
900 <data key="d10">
901 <y:PolyLineEdge>
902 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
903 <y:Point x="481.87400089590056" y="792.1456000000001"/>
904 </y:Path>
905 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
906 <y:Arrows source="none" target="standard"/>
907 <y:BendStyle smoothed="true"/>
908 </y:PolyLineEdge>
909 </data>
910 </edge>
911 <edge id="n2::e20" source="n2::n18" target="n2::n23">
912 <data key="d10">
913 <y:PolyLineEdge>
914 <y:Path sx="0.0" sy="0.0" tx="-3.8586486299604683" ty="19.286400000000015"/>
915 <y:LineStyle color="#00CCFF" type="line" width="1.0"/>
916 <y:Arrows source="none" target="standard"/>
917 <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="34.06740331453011" y="27.999985058593666">
918 <y:LabelModel>
919 <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
920 </y:LabelModel>
921 <y:ModelParameter>
922 <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
923 </y:ModelParameter>
924 <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
925 </y:EdgeLabel>
926 <y:BendStyle smoothed="false"/>
927 </y:PolyLineEdge>
928 </data>
929 </edge>
930 <edge id="n2::e21" source="n2::n19" target="n2::n6">
931 <data key="d10">
932 <y:PolyLineEdge>
933 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
934 <y:LineStyle color="#99CC00" type="line" width="4.0"/>
935 <y:Arrows source="none" target="standard"/>
936 <y:BendStyle smoothed="false"/>
937 </y:PolyLineEdge>
938 </data>
939 </edge>
940 <edge id="n2::e22" source="n2::n20" target="n2::n13">
941 <data key="d10">
942 <y:PolyLineEdge>
943 <y:Path sx="-38.69066605180649" sy="5.1291200000001425" tx="0.0" ty="0.0"/>
944 <y:LineStyle color="#000000" type="dashed" width="1.0"/>
945 <y:Arrows source="none" target="standard"/>
946 <y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="31.9375" horizontalTextPosition="center" iconTextGap="4" lineColor="#999999" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.251953125" x="-18.625970801878566" xml:space="preserve" y="4.994406000976369">mix
947 audio<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.0" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
948 <y:BendStyle smoothed="false"/>
949 </y:PolyLineEdge>
950 </data>
951 </edge>
952 <edge id="n2::e23" source="n2::n20" target="n2::n1">
953 <data key="d10">
954 <y:PolyLineEdge>
955 <y:Path sx="-182.28361093232218" sy="14.994515642151043" tx="-12.973542010080564" ty="-25.02568251200023"/>
956 <y:LineStyle color="#000000" type="dashed" width="1.0"/>
957 <y:Arrows source="none" target="standard"/>
958 <y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="31.9375" horizontalTextPosition="center" iconTextGap="4" lineColor="#999999" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="36.77734375" x="-18.388654451925504" xml:space="preserve" y="4.980108515624806">mix
959 video<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.0" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
960 <y:BendStyle smoothed="false"/>
961 </y:PolyLineEdge>
962 </data>
963 </edge>
964 <edge id="n2::e24" source="n2::n20" target="n2::n27">
965 <data key="d10">
966 <y:PolyLineEdge>
967 <y:Path sx="40.68993107775839" sy="15.013565306270948" tx="0.0" ty="0.0"/>
968 <y:LineStyle color="#000000" type="dashed" width="1.0"/>
969 <y:Arrows source="none" target="standard"/>
970 <y:EdgeLabel alignment="center" backgroundColor="#FFFFFF" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" height="31.9375" horizontalTextPosition="center" iconTextGap="4" lineColor="#999999" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.31640625" x="-23.15821546918869" xml:space="preserve" y="4.986440913085744">blank
971 stream<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="center" ratio="0.0" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
972 <y:BendStyle smoothed="false"/>
973 </y:PolyLineEdge>
974 </data>
975 </edge>
976 <edge id="n2::e25" source="n2::n22" target="n2::n7">
977 <data key="d10">
978 <y:PolyLineEdge>
979 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
980 <y:LineStyle color="#99CC00" type="line" width="4.0"/>
981 <y:Arrows source="none" target="standard"/>
982 <y:BendStyle smoothed="false"/>
983 </y:PolyLineEdge>
984 </data>
985 </edge>
986 <edge id="n2::e26" source="n2::n23" target="n2::n17">
987 <data key="d10">
988 <y:PolyLineEdge>
989 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
990 <y:LineStyle color="#99CC00" type="line" width="1.0"/>
991 <y:Arrows source="none" target="standard"/>
992 <y:BendStyle smoothed="false"/>
993 </y:PolyLineEdge>
994 </data>
995 </edge>
996 <edge id="n2::e27" source="n2::n24" target="n2::n15">
997 <data key="d10">
998 <y:PolyLineEdge>
999 <y:Path sx="0.0" sy="0.0" tx="-0.40503486178886305" ty="-14.141188349529102">
1000 <y:Point x="698.5067196278089" y="840.0"/>
1001 </y:Path>
1002 <y:LineStyle color="#FF6600" type="line" width="4.0"/>
1003 <y:Arrows source="none" target="standard"/>
1004 <y:BendStyle smoothed="true"/>
1005 </y:PolyLineEdge>
1006 </data>
1007 </edge>
1008 <edge id="n2::e28" source="n2::n24" target="n2::n22">
1009 <data key="d10">
1010 <y:PolyLineEdge>
1011 <y:Path sx="0.0" sy="0.0" tx="-0.38729342870021893" ty="-17.13727999999992"/>
1012 <y:LineStyle color="#FF6600" type="line" width="4.0"/>
1013 <y:Arrows source="none" target="standard"/>
1014 <y:BendStyle smoothed="false"/>
1015 </y:PolyLineEdge>
1016 </data>
1017 </edge>
1018 <edge id="n2::e29" source="n2::n25" target="n2::n23">
1019 <data key="d10">
1020 <y:PolyLineEdge>
1021 <y:Path sx="0.0" sy="0.0" tx="-3.882927070767626" ty="-14.286400000000015"/>
1022 <y:LineStyle color="#FF6600" type="line" width="1.0"/>
1023 <y:Arrows source="none" target="standard"/>
1024 <y:BendStyle smoothed="true"/>
1025 </y:PolyLineEdge>
1026 </data>
1027 </edge>
1028 <edge id="n2::e30" source="n2::n25" target="n2::n11">
1029 <data key="d10">
1030 <y:PolyLineEdge>
1031 <y:Path sx="0.0" sy="0.0" tx="0.7138633251405508" ty="-15.572800000000143">
1032 <y:Point x="783.1204293105764" y="677.9999999999998"/>
1033 </y:Path>
1034 <y:LineStyle color="#FF6600" type="line" width="1.0"/>
1035 <y:Arrows source="none" target="standard"/>
1036 <y:BendStyle smoothed="true"/>
1037 </y:PolyLineEdge>
1038 </data>
1039 </edge>
1040 <edge id="n2::e31" source="n2::n31" target="n2::n19">
1041 <data key="d10">
1042 <y:PolyLineEdge>
1043 <y:Path sx="0.0" sy="0.0" tx="1.0399044022113912" ty="11.903188431271133">
1044 <y:Point x="938.7302645604084" y="386.08014843127063"/>
1045 </y:Path>
1046 <y:LineStyle color="#FF6600" type="line" width="4.0"/>
1047 <y:Arrows source="none" target="standard"/>
1048 <y:BendStyle smoothed="true"/>
1049 </y:PolyLineEdge>
1050 </data>
1051 </edge>
1052 <edge id="n2::e32" source="n2::n26" target="n2::n31">
1053 <data key="d10">
1054 <y:PolyLineEdge>
1055 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1056 <y:LineStyle color="#FF6600" type="line" width="1.0"/>
1057 <y:Arrows source="none" target="standard"/>
1058 <y:BendStyle smoothed="false"/>
1059 </y:PolyLineEdge>
1060 </data>
1061 </edge>
1062 <edge id="n2::e33" source="n2::n27" target="n2::n26">
1063 <data key="d10">
1064 <y:PolyLineEdge>
1065 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="-12.605079272727236">
1066 <y:Point x="858.2074547450916" y="314.8508799999996"/>
1067 </y:Path>
1068 <y:LineStyle color="#000000" type="dashed" width="1.0"/>
1069 <y:Arrows source="none" target="standard"/>
1070 <y:BendStyle smoothed="true"/>
1071 </y:PolyLineEdge>
1072 </data>
1073 </edge>
1074 <edge id="n2::e34" source="n2::n27" target="n2::n2">
1075 <data key="d10">
1076 <y:PolyLineEdge>
1077 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="13.949120000000335"/>
1078 <y:LineStyle color="#000000" type="dashed" width="1.0"/>
1079 <y:Arrows source="none" target="standard"/>
1080 <y:BendStyle smoothed="true"/>
1081 </y:PolyLineEdge>
1082 </data>
1083 </edge>
1084 <edge id="n2::e35" source="n2::n1" target="n2::n28">
1085 <data key="d10">
1086 <y:PolyLineEdge>
1087 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1088 <y:LineStyle color="#00CCFF" type="line" width="1.0"/>
1089 <y:Arrows source="none" target="standard"/>
1090 <y:BendStyle smoothed="false"/>
1091 </y:PolyLineEdge>
1092 </data>
1093 </edge>
1094 <edge id="n2::e36" source="n2::n29" target="n2::n19">
1095 <data key="d10">
1096 <y:PolyLineEdge>
1097 <y:Path sx="-0.18883038389446938" sy="-15.626948349528675" tx="-6.819434861788864" ty="-15.626948349528561"/>
1098 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
1099 <y:Arrows source="none" target="standard"/>
1100 <y:BendStyle smoothed="false"/>
1101 </y:PolyLineEdge>
1102 </data>
1103 </edge>
1104 <edge id="n2::e37" source="n2::n30" target="n2::n29">
1105 <data key="d10">
1106 <y:PolyLineEdge>
1107 <y:Path sx="0.0" sy="0.0" tx="-0.358900309852829" ty="-15.910479999999438">
1108 <y:Point x="447.6542042041713" y="358.2664800000001"/>
1109 </y:Path>
1110 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
1111 <y:Arrows source="none" target="standard"/>
1112 <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="27.999998881905697" y="-42.098428388672005">
1113 <y:LabelModel>
1114 <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
1115 </y:LabelModel>
1116 <y:ModelParameter>
1117 <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
1118 </y:ModelParameter>
1119 <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
1120 </y:EdgeLabel>
1121 <y:BendStyle smoothed="true"/>
1122 </y:PolyLineEdge>
1123 </data>
1124 </edge>
1125 <edge id="n2::e38" source="n2::n30" target="n2::n2">
1126 <data key="d10">
1127 <y:PolyLineEdge>
1128 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1129 <y:LineStyle color="#00CCFF" type="line" width="4.0"/>
1130 <y:Arrows source="none" target="standard"/>
1131 <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="134.47344863539865" y="28.00000441406212">
1132 <y:LabelModel>
1133 <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
1134 </y:LabelModel>
1135 <y:ModelParameter>
1136 <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
1137 </y:ModelParameter>
1138 <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
1139 </y:EdgeLabel>
1140 <y:BendStyle smoothed="false"/>
1141 </y:PolyLineEdge>
1142 </data>
1143 </edge>
1144 <edge id="n2::e39" source="n2::n31" target="n2::n10">
1145 <data key="d9"/>
1146 <data key="d10">
1147 <y:PolyLineEdge>
1148 <y:Path sx="0.0" sy="0.0" tx="-0.41771733903499353" ty="36.97456000000011"/>
1149 <y:LineStyle color="#FF6600" type="line" width="1.0"/>
1150 <y:Arrows source="none" target="standard"/>
1151 <y:BendStyle smoothed="false"/>
1152 </y:PolyLineEdge>
1153 </data>
1154 </edge>
1155 <edge id="n2::e40" source="n2::n27" target="n2::n29">
1156 <data key="d9"/>
1157 <data key="d10">
1158 <y:PolyLineEdge>
1159 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
1160 <y:Point x="627.8873167573738" y="314.8508799999996"/>
1161 </y:Path>
1162 <y:LineStyle color="#000000" type="dashed" width="1.0"/>
1163 <y:Arrows source="none" target="standard"/>
1164 <y:BendStyle smoothed="true"/>
1165 </y:PolyLineEdge>
1166 </data>
1167 </edge>
1168 </graph>
1169 <data key="d7">
1170 <y:Resources/>
1171 </data>
1172 </graphml>
0 #!/usr/bin/env bash
1 ##
2 ## entrypoint for the docker images
3
4 if [ ! -f /.dockerenv ] && [ ! -f /.dockerinit ]; then
5 >&2 echo "WARNING: this script should be only run inside docker!!"
6 exit 1
7 fi
8
9 # shellcheck disable=SC2154
10 if [[ ! -z $gid ]] && [[ ! -z $uid ]]; then
11 groupmod -g "${gid}" voc
12 usermod -u "${uid}" -g "${gid}" voc
13
14 # check if homedir is mounted
15 if ! grep -q '/home/voc' /proc/mounts; then
16 # fixup for changed uid and gid
17 chown -R voc:voc /home/voc
18 fi
19 fi
20
21 start_core() {
22 >&2 echo "Starting VoctoMix Core"
23
24 if [[ -x /bin/gosu ]]; then
25 gosu voc /opt/voctomix/voctocore/voctocore.py "$@"
26 else
27 >&2 echo "no gosu binary found..."
28 exec su -l -c "/opt/voctomix/voctocore/voctocore.py -v" voc
29 fi
30 }
31
32 is_video_mounted() {
33 grep -q '/video' /proc/mounts
34 }
35
36 start_gui() {
37 >&2 echo "Starting VoctoMix GUI..."
38
39 if [[ -x /bin/gosu ]]; then
40 gosu voc /opt/voctomix/voctogui/voctogui.py -v
41 else
42 >&2 echo "no gosu binary found..."
43 exec su -l -c "/opt/voctomix/voctogui/voctogui.py -v" voc
44 fi
45 }
46
47 list_examples() {
48 cd example-scripts/
49 find -type f
50 }
51
52 run_example() {
53 if [ -z $1 ]; then
54 echo "no valid example!"
55 fi
56
57 FILENAME="example-scripts/$1"
58 if [[ -f ${FILENAME} ]]; then
59 >&2 echo "Running: ${FILENAME}"
60 ${FILENAME}
61 fi
62 }
63
64 usage() {
65 cat <<-USAGE
66 Usage: $0 <cmd>
67
68 Program for starting voctomix
69
70 COMMANDS:
71
72 help - this text
73 core - starts voctomix core
74 gui - starts voctomix GUI
75 examples - lists the example scripts
76 console - run interactive console
77 scriptname.py - starts the example script named 'scriptname.py'
78 USAGE
79 }
80
81 if [[ $# -le 0 ]]; then
82 usage
83 exit 1
84 fi
85
86 case $1 in
87 help )
88 usage
89 exit 1
90 ;;
91 examples )
92 list_examples
93 exit 0
94 ;;
95 gui)
96 start_gui
97 exit 0
98 ;;
99 core)
100 shift
101 start_core "$@"
102 exit 0
103 ;;
104 console)
105 shift
106 bash "$@"
107 exit 0
108 ;;
109 *)
110 run_example "$1"
111 exit 0
112 ;;
113 esac
+0
-99
docker-ep.sh less more
0 #!/bin/bash
1 ##
2 ## entrypoint for the docker images
3
4 if [ ! -f /.dockerenv ] && [ ! -f /.dockerinit ]; then
5 echo "WARNING: this script should be only run inside docker!!"
6 exit 1
7 fi
8
9 if [ ! -z $gid ] && [ ! -z $uid ]; then
10 groupmod -g $gid voc
11 usermod -u $uid -g $gid voc
12
13 # check if homedir is mounted
14 if grep -q '/home/voc' /proc/mounts; then
15 # homedir is mounted into the docker so don't touch the ownership of the files
16 true
17 else
18 # fixup for changed uid and gid
19 chown -R voc:voc /home/voc
20 fi
21 fi
22
23 function startCore() {
24 echo "Starting Voctomix CORE"
25 if [ -x /bin/gosu ]; then
26 gosu voc /opt/voctomix/voctocore/voctocore.py -v
27 else
28 echo "no gosu found..."
29 exec su -l -c "/opt/voctomix/voctocore/voctocore.py -v" voc
30 fi
31 }
32
33 function isVideoMounted() {
34 return grep -q '/video' /proc/mounts
35 }
36
37 function startGui() {
38 echo "Starting Voctomix GUI..."
39 if [ -x /bin/gosu ]; then
40 gosu voc /opt/voctomix/voctogui/voctogui.py -v
41 else
42 echo "no gosu found..."
43 exec su -l -c "/opt/voctomix/voctogui/voctogui.py -v" voc
44 fi
45 }
46
47 function listExamples() {
48 cd example-scripts/
49 find -type f
50 }
51
52 function runExample() {
53 if [ -z $1 ]; then
54 echo "no valid example! "
55 fi
56 FILENAME="example-scripts/$1"
57 if [ -f ${FILENAME} ]; then
58 echo "Running: ${FILENAME}"
59 ${FILENAME}
60 fi
61 }
62
63 function usage() {
64 echo "Usage: $0 <cmd>"
65 echo "help - this text"
66 echo "core - starts voctomix gore"
67 echo "gui - starts the voctomix GUI"
68 echo "examples - lists the example scripts"
69 echo "bash - run interactive bash"
70 echo "scriptname.py - starts the example script named 'scriptname.py' "
71 }
72
73 if [ -z $1 ]; then
74 usage
75 exit
76 fi
77
78 case $1 in
79 help )
80 usage
81 ;;
82 examples )
83 listExamples
84 ;;
85 gui )
86 startGui
87 ;;
88 core )
89 startCore
90 ;;
91 bash )
92 shift
93 bash $@
94 ;;
95 * )
96 runExample $1
97 ;;
98 esac
0 #!/bin/sh
1
2 ffmpeg -v verbose -nostats -y -analyzeduration 10000 \
3 -thread_queue_size 512 -i tcp://localhost:11000?timeout=3000000 \
4 -thread_queue_size 512 -i tcp://localhost:13003?timeout=3000000 \
5 -aspect 16:9 \
6 -filter_complex \
7 "[0]pan=stereo|c0=c0|c1=c1[a];
8 [0]pan=stereo|c0=c2|c1=c3[b];
9 [0]pan=stereo|c0=c4|c1=c5[c];
10 [0]pan=stereo|c0=c6|c1=c7[d]" \
11 -map 0:v -c:v:0 mpeg2video -pix_fmt:v:0 yuv420p -qscale:v:0 4 -qmin:v:0 4 -qmax:v:0 4 -keyint_min:v:0 5 -bf:v:0 0 -g:v:0 5 -me_method:v:0 dia \
12 -map 1:v -c:v:1 mpeg2video -pix_fmt:v:1 yuv420p -qscale:v:1 4 -qmin:v:1 4 -qmax:v:1 4 -keyint_min:v:1 5 -bf:v:1 0 -g:v:1 5 -me_method:v:1 dia \
13 -map "[a]" -c:a s302m \
14 -map "[b]" -c:a s302m \
15 -map "[c]" -c:a s302m \
16 -map "[d]" -c:a s302m \
17 -flags +global_header \
18 -strict -2 \
19 -f segment -segment_time 180 -strftime 1 -segment_format mpegts "segment-%Y-%m-%d_%H-%M-%S-$$.ts"
20
0 #!/bin/sh
1 ffmpeg -y -nostdin -hide_banner \
2 -thread_queue_size 512 -i tcp://localhost:15000?timeout=3000000 \
3 -thread_queue_size 512 -i tcp://localhost:15001?timeout=3000000 \
4 -filter_complex \
5 "[0:v] hqdn3d [hd];
6 [1:v] scale=1024:576, fps=5, hqdn3d [slides];
7 [0]pan=stereo|c0=c0|c1=c1[a];
8 [0]pan=stereo|c0=c2|c1=c3[b];
9 [0]pan=stereo|c0=c4|c1=c5[c];
10 [0]pan=stereo|c0=c6|c1=c7[d]" \
11 -c:v libx264 -preset:v veryfast -profile:v main -pix_fmt yuv420p -flags +cgop \
12 -threads:v 0 -aspect 16:9 \
13 -map [hd] -metadata:s:v:0 title="HD" \
14 -r:v:0 25 -g:v:0 75 -crf:v:0 21 -maxrate:v:0 4M -bufsize:v:0 18M \
15 -map [slides] -metadata:s:v:1 title="Slides" \
16 -g:v:1 15 -crf:v:1 25 -maxrate:v:1 100k -bufsize:v:1 750k \
17 -c:a aac -b:a 192k -ar 48000 \
18 -map "[a]" \
19 -map "[b]" \
20 -map "[c]" \
21 -map "[d]" \
22 -f matroska \
23 -password password \
24 -content_type video/webm \
25 icecast://host:8000/mountpoint
26
0 #!/bin/sh
1 ffmpeg -y -nostdin -hide_banner \
2 -init_hw_device vaapi=foo:/dev/dri/renderD128 \
3 -hwaccel vaapi \
4 -hwaccel_output_format vaapi \
5 -hwaccel_device foo \
6 -thread_queue_size 1024 -i tcp://localhost:15000?timeout=3000000 \
7 -thread_queue_size 1024 -i tcp://localhost:15001?timeout=3000000 \
8 -filter_hw_device foo \
9 -filter_complex \
10 "[0:v] hqdn3d, format=nv12,hwupload [hd];
11 [1:v] fps=5, hqdn3d, format=nv12,hwupload,scale_vaapi=w=1024:h=576 [slides];
12 [0]pan=stereo|c0=c0|c1=c1[a];
13 [0]pan=stereo|c0=c2|c1=c3[b];
14 [0]pan=stereo|c0=c4|c1=c5[c];
15 [0]pan=stereo|c0=c6|c1=c7[d]" \
16 -map [hd] -metadata:s:v:0 title="HD" \
17 -map [slides] -metadata:s:v:1 title="Slides" \
18 -map "[a]" \
19 -map "[b]" \
20 -map "[c]" \
21 -map "[d]" \
22 -c:v h264_vaapi -flags +cgop -aspect 16:9 -g:v:1 15 -crf:v:1 25 -maxrate:v:1 100k -bufsize:v:1 750k -r:v:0 25 -g:v:0 75 -crf:v:0 21 -maxrate:v:0 4M -bufsize:v:0 18M\
23 -c:a aac -b:a 192k -ar 48000 \
24 -f matroska \
25 -password password \
26 -content_type video/webm \
27 icecast://host:8000/mountpoint
28
11 import socket
22 from lib.config import Config
33 import time
4 import re
45
56 DO_GPIO = True
67 try:
1415
1516 def __init__(self, source, gpio_port, all_gpios=()):
1617 self.source = source
17 self.state = ''
1818 self.gpio_port = gpio_port
1919 if DO_GPIO:
2020 GPIO.setup(all_gpios, GPIO.OUT)
2121 GPIO.output(all_gpios, GPIO.HIGH)
2222
23 def set_state(self, state):
24 self.state = state
23 def update(self, composite_func):
24 restr = "\(|,|\)"
25 cf = re.split(restr, composite_func)
26 if cf[0] == 'fs':
27 if cf[1] == self.source:
28 self.tally_on()
29 else:
30 self.tally_off()
31 else:
32 if self.source in cf[1:]:
33 self.tally_on()
34 else:
35 self.tally_off()
36
2537
2638 def tally_on(self):
2739 if DO_GPIO:
3244 if DO_GPIO:
3345 GPIO.output(self.gpio_port, GPIO.HIGH)
3446 print('Tally off')
35
36 def video_change(self, source_a, source_b):
37 if self.state == 'fullscreen':
38 if source_a == self.source:
39 self.tally_on()
40 else:
41 self.tally_off()
42 else:
43 if self.source in (source_a, source_b):
44 self.tally_on()
45 else:
46 self.tally_off()
4747
4848
4949 def start_connection(tally_handler):
5656 sock.settimeout(None)
5757
5858 messages = []
59 sock.send(b'get_composite_mode\n')
60 sock.send(b'get_video\n')
61 sock.send(b'get_stream_status\n')
59 sock.send(b'get_composite\n')
6260 while True:
6361 if len(messages) == 0:
6462 message = sock.recv(2048)
7472 if len(messages) != 0:
7573 messages = messages[1:]
7674 try:
77 if message[0] == 'composite_mode':
78 tally_handler.set_state(message[1])
79 elif message[0] == 'video_status':
80 source_a, source_b = message[1], message[2]
81 tally_handler.video_change(source_a, source_b)
75 if message[0] == 'composite':
76 composite_func = message[1]
77 tally_handler.update(composite_func)
8278 except IndexError:
8379 pass
8480
0 <?xml version='1.0' encoding='utf-8' ?>
1 <schedule>
2 <day>
3 <room>
4 <event id='1'>
5 <date>2019-01-01T10:00:00+02:00</date>
6 <duration>01:00</duration>
7 <room>HALL 1</room>
8 <title>Interesting talk in HALL 1 at 10:00</title>
9 <persons>
10 <person id='1'>Alice</person>
11 <person id='2'>Bob</person>
12 <person id='3'>Claire</person>
13 </persons>
14 </event>
15 <event id='2'>
16 <date>2019-01-01T10:00:00+02:00</date>
17 <duration>01:00</duration>
18 <room>HALL 2</room>
19 <title>Interesting talk in HALL 2 at 10:00</title>
20 <persons>
21 <person id='4'>Dick</person>
22 </persons>
23 </event>
24 <event id='3'>
25 <date>2019-01-01T11:15:00+02:00</date>
26 <duration>01:00</duration>
27 <room>HALL 2</room>
28 <title>Interesting talk in HALL 2 at 11:15</title>
29 <persons>
30 <person id='1'>Alice</person>
31 <person id='4'>Dick</person>
32 </persons>
33 </event>
34 </room>
35 </day>
36 </schedule>
0 #!/usr/bin/env python3
1 import gi
2 gi.require_version('Gst', '1.0')
3 from gi.repository import Gst
4 import os
5
6 # set GST debug dir for dot files
7 if not 'GST_DEBUG_DUMP_DOT_DIR' in os.environ:
8 os.environ['GST_DEBUG_DUMP_DOT_DIR'] = os.getcwd()
0 #!/usr/bin/env python3
1 import re
2 import logging
3
4 log = logging.getLogger('AudioStreams')
5
6
7 class AudioStreams(list):
8
9 def __init__(self):
10 ''' just init the container '''
11 self = []
12
13 def configure(cfg, source, use_source_as_name=False):
14 ''' create an instance of <AudioStreams> for <source> that gets configured by INI section <cfg>
15 If <use_source_as_name> is True items will be named as <source>.
16 '''
17 audio_streams = AudioStreams()
18
19 # walk through all items within the configuration string
20 for t_name, t in cfg:
21 # search for entrys like 'audio.*'
22 r = re.match(r'^audio\.([\w\-_]+)$', t_name)
23 if r:
24 for i, channel in enumerate(t.split("+")):
25 audio_streams.append(AudioStream(source, i,
26 source if use_source_as_name else r.group(1), channel)
27 )
28 return audio_streams
29
30 def __str__(self):
31 result = ""
32 for index, audio_stream in enumerate(self):
33 result += "mix.%d: %s.%d = %s.%d\n" % (
34 index, audio_stream.name, audio_stream.channel, audio_stream.source, audio_stream.source_channel)
35 return result
36
37 def source_channels(self, source):
38 ''' Return all audio channels by source.
39 '''
40 # collect source's channels into a set and count them
41 return [audio_stream.source_channel for audio_stream in self if audio_stream.source == source]
42
43 def num_channels(self, source, grid=[x for x in range(0, 255)]):
44 ''' Return the number of different audio channels overall or by source.
45 Filter by <source> if given.
46 Round up to values in <grid> to match external capabilities.
47 '''
48 # collect source's channels into a set and count them
49 channels = self.source_channels(source)
50 result = max(channels) + 1 if channels else 0
51 # fill up to values in grid
52 while result not in grid:
53 result += 1
54 return result
55
56 def matrix(self, source, stream=None, out_channels=None, grid=[x for x in range(0, 255)]):
57 ''' Return matrix that maps in to out channels of <source>.
58 Filter by <stream> if given.
59 Fill matrix up to <out_channel> rows if given.
60 Round up number of matrix columns to values in <grid> to match
61 external capabilities.
62 '''
63 # collect result rows
64 result = []
65 for out, audio_stream in enumerate(self):
66 row = []
67 # build result row based on number of channels in that source
68 for ch in range(0, self.num_channels(source, grid)):
69 # map source channels to out channels
70 row.append(1.0
71 if audio_stream.source == source
72 and audio_stream.source_channel == ch
73 and (stream is None or stream == audio_stream.name) else
74 0.0)
75 result.append(row)
76 # if out channels are given
77 if out_channels:
78 # warn if source has more channels than out channels are given
79 if out_channels < len(result):
80 log.error("too many audio channels in source %s", source)
81 else:
82 # append rows up to out_channels
83 result += [[0.0] *
84 self.num_channels(source, grid)] * (out_channels - len(result))
85 return result
86
87 def get_source_streams(self, source):
88 ''' filter all stream channels of given <source> '''
89 result = {}
90 for audio_stream in self:
91 if source == audio_stream.source:
92 if audio_stream.name not in result:
93 result[audio_stream.name] = []
94 result[audio_stream.name].append(audio_stream)
95 return result
96
97 def get_stream_names(self, source=None):
98 ''' Get names of all streams.
99 Filter by <source> if given.
100 '''
101 result = []
102 for audio_stream in self:
103 if not source or source == audio_stream.source:
104 if audio_stream.name not in result:
105 result.append(audio_stream.name)
106 return result
107
108 def get_stream_source(self, source=None):
109 ''' Get sources of all streams.
110 Filter by <source> if given.
111 '''
112 result = []
113 for audio_stream in self:
114 if not source or source == audio_stream.source:
115 if audio_stream.name not in result:
116 result.append(audio_stream.source)
117 return result
118
119 class AudioStream:
120 def __init__(self, source, channel, name, source_channel):
121 ''' initialize stream data '''
122 self.source = source
123 self.channel = int(channel)
124 self.name = name
125 self.source_channel = int(source_channel)
0 def quote(str):
1 ''' encode spaces and comma '''
2 return None if not str else str.replace('\\', '\\\\').replace(' ','\\s').replace('|','\\v').replace(',','\\c').replace('\n','\\n')
3
4 def dequote(str):
5 ''' decode spaces and comma '''
6 return None if not str else str.replace('\\n','\n').replace('\\c', ',').replace('\\v', '|').replace('\\s', ' ').replace('\\\\', '\\')
7
8 def str2bool(str):
9 return str.lower() in [ 'true', 'yes', 'visible', 'show', '1' ]
0 #!/usr/bin/env python3
1 import re
2
3
4 class CompositeCommand:
5
6 def __init__(self, composite, A, B):
7 self.composite = composite
8 self.A = A
9 self.B = B
10
11 def from_str(command):
12 A = None
13 B = None
14 # match case: c(A,B)
15 r = re.match(
16 r'^\s*([|+-]?\w[-_\w]*)\s*\(\s*([-_\w*]+)\s*,\s*([-_\w*]+)\)\s*$', command)
17 if r:
18 A = r.group(2)
19 B = r.group(3)
20 else:
21 # match case: c(A)
22 r = re.match(r'^\s*([|+-]?\w[-_\w]*)\s*\(\s*([-_\w*]+)\s*\)\s*$', command)
23 if r:
24 A = r.group(2)
25 else:
26 # match case: c
27 r = re.match(r'^\s*([|+-]?\w[-_\w]*)\s*$', command)
28 assert r
29 composite = r.group(1)
30 if composite == '*':
31 composite = None
32 if A == '*':
33 A = None
34 if B == '*':
35 B = None
36 return CompositeCommand(composite,A,B)
37
38 def modify(self, mod, reverse=False):
39 # get command as string and process all replactions
40 command = original = str(self)
41 for r in mod.split(','):
42 what, _with = r.split('->')
43 if reverse:
44 what, _with = _with, what
45 command = command.replace(what.strip(), _with.strip())
46 modified = original != command
47 # re-convert string to command and take the elements
48 command = CompositeCommand.from_str(command)
49 self.composite = command.composite
50 self.A = command.A
51 self.B = command.B
52 return modified
53
54 def unmodify(self, mod):
55 return self.modify(mod, True)
56
57 def __str__(self):
58 return "%s(%s,%s)" % (self.composite if self.composite else "*",
59 self.A if self.A else "*",
60 self.B if self.B else "*")
61
62 def __eq__(self, other):
63 return ((self.composite == other.composite or not(self.composite and other.composite))
64 and (self.A == other.A or not(self.A and other.A))
65 and (self.B == other.B or not(self.B and other.B)))
0 #!/usr/bin/env python3
1 # for debug logging
2 import logging
3 # use Frame
4 from vocto.frame import Frame, X, Y, L, T, R, B
5 # for cloning objects
6 import copy
7 # for parsing configuration items
8 import re
9
10 log = logging.getLogger('Composites')
11
12
13 class Composites:
14 """ a namespace for composite related methods
15 """
16
17 def configure(cfg, size, add_swap=True):
18 """ read INI like configuration from <cfg> and return all the defined
19 composites. <size> is the overall frame size which all proportional
20 (floating point) coordinates are related to.
21 """
22 # prepare resulting composites dictonary
23 composites = dict()
24 # walk through composites configuration
25 for c_name, c_val in cfg:
26 if '.' not in c_name:
27 raise RuntimeError("syntax error in composite config '{}' "
28 "(must be: 'name.attribute')"
29 .format(c_name))
30 # split name into name and attribute
31 name, attr = c_name.lower().rsplit('.', 1)
32 if name not in composites:
33 # add new composite
34 composites[name] = Composite(len(composites), name)
35 try:
36 # set attribute
37 composites[name].config(attr, c_val, size)
38 except RuntimeError as err:
39 raise RuntimeError(
40 "syntax error in composite config value at '{}':\n{}"
41 .format(name, err))
42 add_mirrored_composites(composites)
43 if add_swap:
44 # add any useful swapped targets
45 add_swapped_targets(composites)
46 return composites
47
48 def targets(composites):
49 """ return a list of all composites that are not intermediate
50 """
51 result = []
52 for c_name, c in composites.items():
53 if not c.inter:
54 result.append(c)
55 return sorted(result, key=lambda c: c.order)
56
57 def intermediates(composites):
58 """ return a list of all composites that are intermediate
59 """
60 result = []
61 for c_name, c in composites.items():
62 if c.inter:
63 result.append(c)
64 return sorted(result, key=lambda c: c.order)
65
66
67 class Composite:
68
69 def __init__(self, order, name, a=Frame(True), b=Frame(True)):
70 assert type(order) is int or order is None
71 assert type(name) is str or not name
72 self.name = name
73 self.frame = [copy.deepcopy(a), copy.deepcopy(b)]
74 self.default = [None, None]
75 self.inter = False
76 self.noswap = False
77 self.mirror = False
78 self.order = order
79
80 def str_title():
81 return "Key A%s\tB%s Name" % (Frame.str_title(), Frame.str_title())
82
83 def __str__(self):
84 def hidden( x, hidden ):
85 return str(x).replace(' ','_') if hidden else str(x)
86
87 return "%s A%s\tB%s %s" % (" * " if self.A().key else " ",
88 hidden(self.A(), self.A().invisible() or self.covered()),
89 hidden(self.B(), self.B().invisible()),
90 self.name)
91
92 def equals(self, other, treat_covered_as_invisible, swapped=False):
93 """ compare two composites if they are looking the same
94 (e.g. a rectangle with size 0x0=looks the same as one with alpha=0
95 and so it is treated as equal here)
96 """
97 if not swapped:
98 if not (self.A() == other.A() or (treat_covered_as_invisible and self.covered() and other.covered())):
99 return False
100 elif not (self.B() == other.B() or (self.B().invisible() and other.B().invisible())):
101 return False
102 else:
103 if not (self.A() == other.B() or (treat_covered_as_invisible and self.covered() and other.B().invisible())):
104 return False
105 elif not (self.B() == other.A() or (self.B().invisible() and other.covered())):
106 return False
107 return True
108
109 def A(self):
110 return self.frame[0]
111
112 def B(self):
113 return self.frame[1]
114
115 def Az(self, zorder):
116 frame = copy.deepcopy(self.frame[0])
117 frame.zorder = zorder
118 return frame
119
120 def Bz(self, zorder):
121 frame = copy.deepcopy(self.frame[1])
122 frame.zorder = zorder
123 return frame
124
125 def swapped(self):
126 """ swap A and B source items
127 """
128 if self.noswap:
129 return self
130 else:
131 # deep copy everything
132 s = copy.deepcopy(self)
133 # then swap frames
134 s.frame = self.frame[::-1]
135 s.name = swap_name(self.name)
136 return s
137
138 def mirrored(self):
139 """ mirror A and B source items
140 """
141 # deep copy everything
142 s = copy.copy(self)
143 # then mirror frames
144 s.frame = [f.mirrored() for f in self.frame]
145 s.name = mirror_name(self.name)
146 return s
147
148 def key(self):
149 for f in self.frame:
150 if f.key:
151 return True
152 return False
153
154 def config(self, attr, value, size):
155 """ set value <value> from INI attribute <attr>.
156 <size> is the input channel size
157 """
158 if attr == 'a':
159 self.frame[0].rect = str2rect(value, size)
160 elif attr == 'b':
161 self.frame[1].rect = str2rect(value, size)
162 elif attr == 'crop-a':
163 self.frame[0].crop = str2crop(value, size)
164 elif attr == 'crop-b':
165 self.frame[1].crop = str2crop(value, size)
166 elif attr == 'default-a':
167 self.default[0] = value
168 elif attr == 'default-b':
169 self.default[1] = value
170 elif attr == 'alpha-a':
171 self.frame[0].alpha = str2alpha(value)
172 elif attr == 'alpha-b':
173 self.frame[1].alpha = str2alpha(value)
174 elif attr == 'inter':
175 self.inter = value
176 elif attr == 'noswap':
177 self.noswap = value
178 elif attr == 'mirror':
179 self.mirror = value
180 self.frame[0].original_size = size
181 self.frame[1].original_size = size
182
183 def covered(self):
184 """ check if below (A) is invisible or covered by above (B)
185 (considers shape with cropping and transparency)
186 """
187 below, above = self.frame
188 if below.invisible():
189 return True
190 if above.invisible():
191 return False
192 bc = below.cropped()
193 ac = above.cropped()
194 # return if above is (semi-)transparent or covers below completely
195 return (above.alpha == 255 and
196 bc[L] >= ac[L] and
197 bc[T] >= ac[T] and
198 bc[R] <= ac[R] and
199 bc[B] <= ac[B])
200
201 def single(self):
202 """ check if above (B) is invisible
203 """
204 below, above = self.frame
205 return above.invisible()
206
207 def both(self):
208 return not (single() or covered())
209
210
211 def add_swapped_targets(composites):
212 result = dict()
213 for c_name, c in composites.items():
214 if not (c.inter or c.noswap):
215 inc = True
216 for v_name, v in composites.items():
217 if v.equals(c.swapped(), True) and not v.inter:
218 inc = False
219 break
220 if inc:
221 log.debug("Adding auto-swapped target %s from %s" %
222 (swap_name(c_name), c_name))
223 r = c.swapped()
224 r.order = len(composites) + len(result)
225 result[swap_name(c_name)] = r
226 return composites.update(result)
227
228 def add_mirrored_composites(composites):
229 result = dict()
230 for c_name, c in composites.items():
231 if c.mirror:
232 r = c.mirrored()
233 r.order = len(composites) + len(result)
234 result[mirror_name(c_name)] = r
235 return composites.update(result)
236
237
238 def swap_name(name): return name[1:] if name[0] == '^' else "^" + name
239 def mirror_name(name): return name[1:] if name[0] == '|' else "|" + name
240
241
242 def absolute(str, max):
243 if str == '*':
244 assert max
245 # return maximum value
246 return int(max)
247 elif '.' in str:
248 assert max
249 # return absolute (Pixel) value in proportion to max
250 return int(float(str) * max)
251 else:
252 # return absolute (Pixel) value
253 return int(str)
254
255
256 def str2rect(str, size):
257 """ read rectangle pair from string '*', 'X/Y WxH', 'X/Y', 'WxH', 'X/Y WH', 'X/Y WH' or 'XY WH'
258 """
259 # check for '*'
260 if str == "*":
261 # return overall position and size
262 return [0, 0, size[X], size[Y]]
263
264 # check for 'X/Y'
265 r = re.match(r'^\s*([-.\d]+)\s*/\s*([-.\d]+)\s*$', str)
266 if r:
267 # return X,Y and overall size
268 return [absolute(r.group(1), size[X]),
269 absolute(r.group(2), size[Y]),
270 size[X],
271 size[Y]]
272 # check for 'WxH'
273 r = re.match(r'^\s*([.\d]+)\s*x\s*([.\d]+)\s*$', str)
274 if r:
275 # return overall pos and W,H
276 return [0,
277 0,
278 absolute(r.group(3), size[X]),
279 absolute(r.group(4), size[Y])]
280 # check for 'X/Y WxH'
281 r = re.match(
282 r'^\s*([-.\d]+)\s*/\s*([-.\d]+)\s+([.\d]+)\s*x\s*([.\d]+)\s*$', str)
283 if r:
284 # return X,Y,X+W,Y+H
285 return [absolute(r.group(1), size[X]),
286 absolute(r.group(2), size[Y]),
287 absolute(r.group(1), size[X]) + absolute(r.group(3), size[X]),
288 absolute(r.group(2), size[Y]) + absolute(r.group(4), size[Y])]
289 # check for 'XY WxH'
290 r = re.match(r'^\s*(-?\d+.\d+)\s+([.\d]+)\s*x\s*([.\d]+)\s*$', str)
291 if r:
292 # return XY,XY,XY+W,XY+H
293 return [absolute(r.group(1), size[X]),
294 absolute(r.group(1), size[Y]),
295 absolute(r.group(1), size[X]) + absolute(r.group(2), size[X]),
296 absolute(r.group(1), size[Y]) + absolute(r.group(3), size[Y])]
297 # check for 'X/Y WH'
298 r = re.match(r'^\s*([-.\d]+)\s*/\s*([-.\d]+)\s+(\d+.\d+)\s*$', str)
299 if r:
300 # return X,Y,X+WH,Y+WH
301 return [absolute(r.group(1), size[X]),
302 absolute(r.group(2), size[Y]),
303 absolute(r.group(1), size[X]) + absolute(r.group(3), size[X]),
304 absolute(r.group(2), size[Y]) + absolute(r.group(3), size[Y])]
305 # check for 'XY WH'
306 r = re.match(r'^\s*(-?\d+.\d+)\s+(\d+.\d+)\s*$', str)
307 if r:
308 # return XY,XY,XY+WH,XY+WH
309 return [absolute(r.group(1), size[X]),
310 absolute(r.group(1), size[Y]),
311 absolute(r.group(1), size[X]) + absolute(r.group(2), size[X]),
312 absolute(r.group(1), size[Y]) + absolute(r.group(2), size[Y])]
313 # didn't get it
314 raise RuntimeError("syntax error in rectangle value '{}' "
315 "(must be either '*', 'X/Y WxH', 'X/Y', 'WxH', 'X/Y WH', 'X/Y WH' or 'XY WH' where X, Y, W, H may be int or float and XY, WH must be float)".format(str))
316
317
318 def str2crop(str, size):
319 """ read crop values pair from string '*' or 'L/T/R/B'
320 """
321 # check for '*'
322 if str == "*":
323 # return zero borders
324 return [0, 0, 0, 0]
325 # check for L/T/R/B
326 r = re.match(
327 r'^\s*([.\d]+)\s*/\s*([.\d]+)\s*/\s*([.\d]+)\s*/\s*([.\d]+)\s*$', str)
328 if r:
329 return [absolute(r.group(1), size[X]),
330 absolute(r.group(2), size[Y]),
331 absolute(r.group(3), size[X]),
332 absolute(r.group(4), size[Y])]
333 # check for LR/TB
334 r = re.match(
335 r'^\s*([.\d]+)\s*/\s*([.\d]+)\s*$', str)
336 if r:
337 return [absolute(r.group(1), size[X]),
338 absolute(r.group(2), size[Y]),
339 absolute(r.group(1), size[X]),
340 absolute(r.group(2), size[Y])]
341 # check for LTRB
342 r = re.match(
343 r'^\s*([.\d]+)\s*$', str)
344 if r:
345 return [absolute(r.group(1), size[X]),
346 absolute(r.group(1), size[Y]),
347 absolute(r.group(1), size[X]),
348 absolute(r.group(1), size[Y])]
349 # didn't get it
350 raise RuntimeError("syntax error in crop value '{}' "
351 "(must be either '*', 'L/T/R/B', 'LR/TB', 'LTRB' where L, T, R, B, LR/TB and LTRB must be int or float')".format(str))
352
353
354 def str2alpha(str):
355 """ read alpha values from string as float between 0.0 and 1.0 or as int between 0 an 255
356 """
357 # check for floating point value
358 r = re.match(
359 r'^\s*([.\d]+)\s*$', str)
360 if r:
361 # return absolute proportional to 255
362
363 return absolute(r.group(1), 255)
364 # didn't get it
365 raise RuntimeError("syntax error in alpha value '{}' "
366 "(must be float or int)".format(str))
0 #!/usr/bin/env python3
1 import logging
2 import re
3 import os
4
5 from gi.repository import Gst
6 from configparser import SafeConfigParser
7 from lib.args import Args
8 from vocto.transitions import Composites, Transitions
9 from vocto.audio_streams import AudioStreams
10
11 testPatternCount = 0
12
13 GST_TYPE_VIDEO_TEST_SRC_PATTERN = [
14 "smpte",
15 "ball",
16 "red",
17 "green",
18 "blue",
19 "black",
20 "white",
21 "checkers-1",
22 "checkers-2",
23 "checkers-4",
24 "checkers-8",
25 "circular",
26 "blink",
27 "smpte75",
28 "zone-plate",
29 "gamut",
30 "chroma-zone-plate",
31 "solid-color",
32 "smpte100",
33 "bar",
34 "snow",
35 "pinwheel",
36 "spokes",
37 "gradient",
38 "colors"
39 ]
40
41 GST_TYPE_AUDIO_TEST_SRC_WAVE = [
42 "sine",
43 "square",
44 "saw",
45 "triangle",
46 "silence",
47 "white-noise",
48 "pink-noise",
49 "sine-table",
50 "ticks",
51 "gaussian-noise",
52 "red-noise",
53 "blue-noise",
54 "violet-noise",
55 ]
56
57
58 class VocConfigParser(SafeConfigParser):
59
60 log = logging.getLogger('VocConfigParser')
61
62 def getList(self, section, option, fallback=None):
63 if self.has_option(section, option):
64 option = self.get(section, option).strip()
65 if len(option) == 0:
66 return []
67
68 unfiltered = [x.strip() for x in option.split(',')]
69 return list(filter(None, unfiltered))
70 else:
71 return fallback
72
73 def getSources(self):
74 return self.getList('mix', 'sources')
75
76 def getAudioSource(self):
77 return self.get('mix', 'audiosource', fallback=None)
78
79 def getLiveSources(self):
80 return ["mix"] + self.getList('mix', 'livesources', [])
81
82 def getBackgroundSources(self):
83 if self.has_option('mix', 'backgrounds'):
84 return self.getList('mix', 'backgrounds')
85 else:
86 return ["background"]
87
88 def getBackgroundSource(self,composite):
89 for source in self.getBackgroundSources():
90 if composite in self.getList('source.{}'.format(source), 'composites', fallback=[]):
91 return source
92 return self.getBackgroundSources()[0]
93
94 def getSourceKind(self, source):
95 return self.get('source.{}'.format(source), 'kind', fallback='test')
96
97 def getNoSignal(self):
98 nosignal = self.get('mix', 'nosignal', fallback='smpte100').lower()
99 if nosignal in ['none','false','no']:
100 return None
101 elif nosignal in GST_TYPE_VIDEO_TEST_SRC_PATTERN:
102 return nosignal
103 else:
104 self.log.error("Configuration value mix/nosignal has unknown pattern '{}'".format(nosignal))
105
106 def getDeckLinkDeviceNumber(self, source):
107 return self.getint('source.{}'.format(source), 'devicenumber', fallback=0)
108
109 def getDeckLinkAudioConnection(self, source):
110 return self.get('source.{}'.format(source), 'audio_connection', fallback='auto')
111
112 def getDeckLinkVideoConnection(self, source):
113 return self.get('source.{}'.format(source), 'video_connection', fallback='auto')
114
115 def getDeckLinkVideoMode(self, source):
116 return self.get('source.{}'.format(source), 'video_mode', fallback='auto')
117
118 def getDeckLinkVideoFormat(self, source):
119 return self.get('source.{}'.format(source), 'video_format', fallback='auto')
120
121 def getV4l2Device(self, source):
122 return self.get('source.{}'.format(source), 'device', fallback='/dev/video0')
123
124 def getV4l2Width(self, source):
125 return self.get('source.{}'.format(source), 'width', fallback=1920)
126
127 def getV4l2Height(self, source):
128 return self.get('source.{}'.format(source), 'height', fallback=1080)
129
130 def getV4l2Format(self, source):
131 return self.get('source.{}'.format(source), 'format', fallback='YUY2')
132
133 def getV4l2Framerate(self, source):
134 return self.get('source.{}'.format(source), 'framerate', fallback='25/1')
135
136 def getImageURI(self,source):
137 if self.has_option('source.{}'.format(source), 'imguri'):
138 return self.get('source.{}'.format(source), 'imguri')
139 else:
140 path = os.path.abspath(self.get('source.{}'.format(source), 'file'))
141 if not os.path.isfile(path):
142 self.log.error("image file '%s' could not be found" % path)
143 return "file://{}".format(path)
144
145 def getLocation(self,source):
146 return self.get('source.{}'.format(source), 'location')
147
148 def getLoop(self,source):
149 return self.get('source.{}'.format(source), 'loop', fallback="true")
150
151 def getTestPattern(self, source):
152 if not self.has_section('source.{}'.format(source)):
153 # default blinder source shall be smpte (if not defined otherwise)
154 if source == "blinder":
155 return "smpte"
156 # default background source shall be black (if not defined otherwise)
157 if source in self.getBackgroundSources():
158 return "black"
159
160 pattern = self.get('source.{}'.format(source), 'pattern', fallback=None)
161 if not pattern:
162 global testPatternCount
163 testPatternCount += 1
164 pattern = GST_TYPE_VIDEO_TEST_SRC_PATTERN[testPatternCount % len(GST_TYPE_VIDEO_TEST_SRC_PATTERN)]
165 self.log.info("Test pattern of source '{}' unspecified, picking '{} ({})'"
166 .format(source,pattern, testPatternCount))
167 return pattern
168
169 def getTestWave(self, source):
170 if not self.has_section('source.{}'.format(source)):
171 # background needs no sound, blinder should have no sound
172 if source == "blinder" or source == "background":
173 return "silence"
174
175 return self.get('source.{}'.format(source), 'wave', fallback="sine")
176
177 def getSourceScan(self, source):
178 section = 'source.{}'.format(source)
179 if self.has_option(section, 'deinterlace'):
180 self.log.error("source attribute 'deinterlace' is obsolete. Use 'scan' instead! Falling back to 'progressive' scheme")
181 return self.get(section, 'scan', fallback='progressive')
182
183 def getAudioStreams(self):
184 audio_streams = AudioStreams()
185 sources = self.getSources()
186 for source in sources:
187 section = 'source.{}'.format(source)
188 if self.has_section(section):
189 audio_streams += AudioStreams.configure(self.items(section), source)
190 return audio_streams
191
192 def getBlinderAudioStreams(self):
193 audio_streams = AudioStreams()
194 section = 'source.blinder'
195 if self.has_section(section):
196 audio_streams += AudioStreams.configure(self.items(section), "blinder", use_source_as_name=True)
197 return audio_streams
198
199 def getAudioStream(self, source):
200 section = 'source.{}'.format(source)
201 if self.has_section(section):
202 return AudioStreams.configure(self.items(section), source)
203 return AudioStreams()
204
205 def getNumAudioStreams(self):
206 num_audio_streams = len(self.getAudioStreams())
207 if self.getAudioChannels() < num_audio_streams:
208 self.log.error("number of audio channels in mix/audiocaps differs from the available audio input channels within the sources!")
209 return num_audio_streams
210
211 def getVideoCaps(self):
212 return self.get('mix', 'videocaps', fallback="video/x-raw,format=I420,width=1920,height=1080,framerate=25/1,pixel-aspect-ratio=1/1")
213
214 def getAudioCaps(self, section='mix'):
215 return self.get(section, 'audiocaps', fallback="audio/x-raw,format=S16LE,channels=2,layout=interleaved,rate=48000")
216
217 def getAudioChannels(self):
218 caps = Gst.Caps.from_string(
219 self.getAudioCaps()).get_structure(0)
220 _, channels = caps.get_int('channels')
221 return channels
222
223 def getVideoResolution(self):
224 caps = Gst.Caps.from_string(
225 self.getVideoCaps()).get_structure(0)
226 _, width = caps.get_int('width')
227 _, height = caps.get_int('height')
228 return (width, height)
229
230 def getVideoRatio(self):
231 width, height = self.getVideoResolution()
232 return float(width)/float(height)
233
234 def getFramerate(self):
235 caps = Gst.Caps.from_string(
236 self.getVideoCaps()).get_structure(0)
237 (_, numerator, denominator) = caps.get_fraction('framerate')
238 return (numerator, denominator)
239
240 def getFramesPerSecond(self):
241 num, denom = self.getFramerate()
242 return float(num) / float(denom)
243
244 def getVideoSystem(self):
245 return self.get('videodisplay', 'system', fallback='gl')
246
247 def getPlayAudio(self):
248 return self.getboolean('audio', 'play', fallback=False)
249
250 def getVolumeControl(self):
251 # Check if there is a fixed audio source configured.
252 # If so, we will remove the volume sliders entirely
253 # instead of setting them up.
254 return (self.getboolean('audio', 'volumecontrol', fallback=True)
255 or self.getboolean('audio', 'forcevolumecontrol', fallback=False))
256
257 def getBlinderEnabled(self):
258 return self.getboolean('blinder', 'enabled', fallback=False)
259
260 def isBlinderDefault(self):
261 return not self.has_option('blinder', 'videos')
262
263 def getBlinderSources(self):
264 if self.getBlinderEnabled():
265 if self.isBlinderDefault():
266 return ["blinder"]
267 else:
268 return self.getList('blinder', 'videos')
269 else:
270 return []
271
272 def getBlinderVolume(self):
273 return self.getfloat('source.blinder', 'volume', fallback=1.0)
274
275 def getMirrorsEnabled(self):
276 return self.getboolean('mirrors', 'enabled', fallback=False)
277
278 def getMirrorsSources(self):
279 if self.getMirrorsEnabled():
280 if self.has_option('mirrors', 'sources'):
281 return self.getList('mirrors', 'sources')
282 else:
283 return self.getSources()
284 else:
285 return []
286
287 def getOutputBuffers(self, channel):
288 return self.getint('output-buffers', channel, fallback=500)
289
290 def getPreviewVaapi(self):
291 if self.has_option('previews', 'vaapi'):
292 return self.get('previews', 'vaapi')
293 return None
294
295 def getDenoiseVaapi(self):
296 if self.has_option('previews', 'vaapi-denoise'):
297 if self.getboolean('previews', 'vaapi-denoise'):
298 return 1
299 return 0
300
301 def getScaleMethodVaapi(self):
302 if self.has_option('previews', 'scale-method'):
303 return self.getint('previews', 'scale-method')
304 return 0
305
306 def getPreviewCaps(self):
307 if self.has_option('previews', 'videocaps'):
308 return self.get('previews', 'videocaps')
309 else:
310 return self.getVideoCaps()
311
312 def getPreviewSize(self):
313 width = self.getint('previews', 'width') if self.has_option(
314 'previews', 'width') else 320
315 height = self.getint('previews', 'height') if self.has_option(
316 'previews', 'height') else int(width * 9 / 16)
317 return(width, height)
318
319 def getPreviewFramerate(self):
320 caps = Gst.Caps.from_string(
321 self.getPreviewCaps()).get_structure(0)
322 (_, numerator, denominator) = caps.get_fraction('framerate')
323 return (numerator, denominator)
324
325 def getPreviewResolution(self):
326 caps = Gst.Caps.from_string(
327 self.getPreviewCaps()).get_structure(0)
328 _, width = caps.get_int('width')
329 _, height = caps.get_int('height')
330 return (width, height)
331
332 def getDeinterlacePreviews(self):
333 return self.getboolean('previews', 'deinterlace', fallback=False)
334
335 def getPreviewsEnabled(self):
336 return self.getboolean('previews', 'enabled', fallback=False)
337
338 def getLivePreviews(self):
339 if self.getBlinderEnabled():
340 singleval = self.get('previews', 'live').lower()
341 if singleval in [ "true", "yes" ]:
342 return ["mix"]
343 if singleval == "all":
344 return self.getLiveSources()
345 previews = self.getList('previews', 'live')
346 result = []
347 for preview in previews:
348 if preview not in self.getLiveSources():
349 self.log.error("source '{}' configured in 'preview/live' must be listed in 'mix/livesources'!".format(preview))
350 else:
351 result.append(preview)
352 return result
353 else:
354 self.log.warning("configuration attribute 'preview/live' is set but blinder is not in use!")
355 return []
356
357 def getPreviewDecoder(self):
358 if self.has_option('previews', 'vaapi'):
359 return self.get('previews', 'vaapi')
360 else:
361 return 'jpeg'
362
363 def getComposites(self):
364 return Composites.configure(self.items('composites'), self.getVideoResolution())
365
366 def getTargetComposites(self):
367 return Composites.targets(self.getComposites())
368
369 def getTransitions(self, composites):
370 return Transitions.configure(self.items('transitions'),
371 composites,
372 fps=self.getFramesPerSecond())
373
374 def getPreviewNameOverlay(self):
375 return self.getboolean('previews', 'nameoverlay', fallback=True)
376
377 def hasSource(self, source):
378 return self.has_section('source.{}'.format(source))
379
380 def hasOverlay(self):
381 return self.has_section('overlay')
382
383 def getOverlayAutoOff(self):
384 return self.getboolean('overlay', 'auto-off', fallback=True)
385
386 def getOverlayUserAutoOff(self):
387 return self.getboolean('overlay', 'user-auto-off', fallback=False)
0 #!/usr/bin/env python3
1 import os
2 import logging
3 from gi.repository import Gst
4 import gi
5 gi.require_version('Gst', '1.0')
6
7 log = logging.getLogger('vocto.debug')
8
9 def gst_generate_dot(pipeline, name):
10 from lib.args import Args
11 dotfile = os.path.join(os.environ['GST_DEBUG_DUMP_DOT_DIR'], "%s.dot" % name)
12 log.debug("Generating DOT image of pipeline '{name}' into '{file}'".format(name=name, file=dotfile))
13 Gst.debug_bin_to_dot_file(pipeline, Gst.DebugGraphDetails(Args.gst_debug_details), name)
14
15
16 gst_log_messages_lastmessage = None
17 gst_log_messages_lastlevel = None
18 gst_log_messages_repeat = 0
19
20 def gst_log_messages(level):
21
22 gstLog = logging.getLogger('Gst')
23
24 def log( level, msg ):
25 if level == Gst.DebugLevel.WARNING:
26 gstLog.warning(msg)
27 if level == Gst.DebugLevel.FIXME:
28 gstLog.warning(msg)
29 elif level == Gst.DebugLevel.ERROR:
30 gstLog.error(msg)
31 elif level == Gst.DebugLevel.INFO:
32 gstLog.info(msg)
33 elif level == Gst.DebugLevel.DEBUG:
34 gstLog.debug(msg)
35
36 def logFunction(category, level, file, function, line, object, message, *user_data):
37 global gst_log_messages_lastmessage, gst_log_messages_lastlevel, gst_log_messages_repeat
38
39 msg = message.get()
40 if gst_log_messages_lastmessage != msg:
41 if gst_log_messages_repeat > 2:
42 log(gst_log_messages_lastlevel,"%s [REPEATING %d TIMES]" % (gst_log_messages_lastmessage, gst_log_messages_repeat))
43
44 gst_log_messages_lastmessage = msg
45 gst_log_messages_repeat = 0
46 gst_log_messages_lastlevel = level
47 log(level,"%s: %s (in function %s() in file %s:%d)" % (object.name if object else "", msg, function, file, line))
48 else:
49 gst_log_messages_repeat += 1
50
51
52 Gst.debug_remove_log_function(None)
53 Gst.debug_add_log_function(logFunction,None)
54 Gst.debug_set_default_threshold(level)
55 Gst.debug_set_active(True)
0 #!/usr/bin/env python3
1 # for debug logging
2 import logging
3 # for cloning objects
4 import copy
5
6 # substitute array coordinate mappings fer better reading
7 X, Y = 0, 1
8 L, T, R, B = 0, 1, 2, 3
9
10 log = logging.getLogger('Frame')
11
12
13 class Frame:
14
15 def __init__(self, key=False, alpha=255, zorder=None, rect=[0, 0, 0, 0], crop=[0, 0, 0, 0]):
16 self.rect = rect
17 self.crop = crop
18 self.alpha = alpha
19 self.original_size = [0.0, 0.0]
20 self.key = key
21 self.zorder = zorder
22
23 def __repr__(self):
24 z = [round(x, 1) for x in self.zoom]
25 return ("{0.rect} {0.crop} {0.alpha} {1}").format(self, z)
26
27 def str_title():
28 return "( L, T R, B alpha LCRP,TCRP,RCRP,BCRP XZOM,YZOM, Z)"
29
30 def __str__(self):
31 return ("(%4d,%4d %4d,%4d %4d %4d,%4d,%4d,%4d %1.2f,%1.2f, %2d)" %
32 tuple(self.rect + [self.alpha] + self.crop + self.zoom() + [self.zorder if self.zorder is not None else -1]))
33
34 def __eq__(self, other):
35 # do NOT compare zoom
36 return self.rect == other.rect and self.crop == other.crop and self.alpha == other.alpha
37
38 def zoomx(self):
39 """ calculate x-zoom factor from relation between given size and
40 width of rect in all channels
41 """
42 if self.crop != [0, 0, 0, 0]:
43 return (self.rect[R] - self.rect[L]) / self.original_size[X]
44 return 0.0
45
46 def zoomy(self):
47 """ calculate zoom factors from relation between given size and
48 width and height of rect in all channels
49 """
50 if self.crop != [0, 0, 0, 0]:
51 return (self.rect[B] - self.rect[T]) / self.original_size[Y]
52 return 0.0
53
54
55 def zoom(self):
56 """ calculate zoom factors from relation between given size and
57 width and height of rect in all channels
58 """
59 return [self.zoomx(), self.zoomy()]
60
61 def cropped(self):
62 if not self.rect:
63 return None
64 return [self.rect[L] + self.crop[L] * self.zoomx(),
65 self.rect[T] + self.crop[T] * self.zoomy(),
66 self.rect[R] - self.crop[R] * self.zoomx(),
67 self.rect[B] - self.crop[B] * self.zoomy()]
68
69 def corner(self, ix, iy): return [self.rect[ix], self.rect[iy]]
70
71 def left(self): return self.rect[L]
72
73 def top(self): return self.rect[T]
74
75 def width(self): return self.rect[R] - self.rect[L]
76
77 def height(self): return self.rect[B] - self.rect[T]
78
79 def float_alpha(self):
80 return float(self.alpha)/255.0
81
82 def size(self): return self.width(), self.height()
83
84 def invisible(self):
85 return (self.rect is None or
86 self.rect[R] == self.rect[L] or
87 self.rect[T] == self.rect[B] or
88 self.alpha == 0)
89
90 def mirrored(self):
91 # deep copy everything
92 f = self.duplicate()
93 # then mirror frame
94 f.rect[L], f.rect[R] = f.original_size[X] - f.rect[R], f.original_size[X] - f.rect[L]
95 return f
96
97 def duplicate(self):
98 return copy.deepcopy(self)
0 #!/usr/bin/env python3
1 import json
2
3
4 class Port(object):
5
6 NONE = 0
7
8 IN = 1
9 OUT = 2
10
11 OFFSET_PREVIEW = 100
12 # core listening port
13 CORE_LISTENING = 9999
14 # input ports
15 SOURCES_IN = 10000
16 SOURCES_BACKGROUND = 16000
17 SOURCE_OVERLAY= 14000
18 SOURCES_BLANK = 17000
19 AUDIO_SOURCE_BLANK = 18000
20 # output ports
21 MIX_OUT = 11000
22 MIX_PREVIEW = MIX_OUT+OFFSET_PREVIEW
23 SOURCES_OUT = 13000
24 SOURCES_PREVIEW = SOURCES_OUT+OFFSET_PREVIEW
25 LIVE_OUT = 15000
26 LIVE_PREVIEW = LIVE_OUT+OFFSET_PREVIEW
27
28 def __init__(self, name, source=None, audio=None, video=None):
29 self.name = name
30 self.source = source
31 self.audio = audio
32 self.video = video
33 self.update()
34
35 def todict(self):
36 return {
37 'name': self.name,
38 'port': self.port,
39 'audio': self.audio,
40 'video': self.video,
41 'io': self.io,
42 'connections': self.connections
43 }
44
45 def update(self):
46 if self.source:
47 self.port = self.source.port()
48 self.audio = self.source.audio_channels()
49 self.video = self.source.video_channels()
50 self.io = self.IN if self.source.is_input() else self.OUT
51 self.connections = self.source.num_connections()
52
53 def from_str(_str):
54 p = Port(_str['name'])
55 p.port = _str['port']
56 p.audio = _str['audio']
57 p.video = _str['video']
58 p.io = _str['io']
59 p.connections = _str['connections']
60 return p
61
62 def is_input(self):
63 return self.io == Port.IN
64
65 def is_output(self):
66 return self.io == Port.OUT
0 # for parsing configuration items
1 import re
2
3 def pretty(pipe):
4 result = ""
5 for line in pipe.splitlines():
6 line = line.strip()
7 r = re.match(r'^(!\s.*)$', line)
8 if r:
9 result += " " + r.group(1)
10 else:
11 r = re.match(r'^([\w\-_]*\s*\=\s*.*)$', line)
12 if r:
13 result += " " + r.group(1)
14 else:
15 r = re.match(r'^(\))$', line)
16 if r:
17 result += r.group(1)
18 else:
19 r = re.match(r'^bin.\($', line)
20 if r:
21 result += line
22 else:
23 result += " " + line
24 result += "\n"
25 return result
0 #!/usr/bin/env python3
1 # for debug logging
2 import logging
3 from vocto.composites import Composite, Composites, swap_name
4 from vocto.frame import Frame, L, R, T, B, X, Y
5 # for calculating square roots
6 import math
7 # for generating B-Splines
8 from scipy import interpolate as spi
9 # for converting arrays
10 import numpy as np
11 # for cloning objects
12 import copy
13
14 V = 2 # distance (velocity) index
15
16 log = logging.getLogger('Transitions')
17 logKeyFramesOnly = True
18
19 class Transitions:
20 """ transition table and interface
21 """
22
23 # interpolation resolution
24 HiRes = 0.001
25 LoRes = 0.01
26 resolution = HiRes
27
28 def __init__(self, targets=[], fps=25):
29 self.transitions = []
30 self.targets = targets
31 self.fps = fps
32
33 def __str__(self):
34 """ write transition table into a string
35 """
36 return "\n".join([t.name() for t in self.transitions])
37
38 def __len__(self):
39 return len(self.transitions)
40
41 def count(self):
42 """ count available transition
43 """
44 return len(self.transitions)
45
46 def add(self,transition,frames):
47 # check if a compatible transition is already in our pool
48 for t in self.transitions:
49 if t.begin().equals(transition.begin(), True) and t.end().equals(transition.end(), True):
50 # skip if found
51 return
52 elif t.begin().equals(transition.end(), True) and t.end().equals(transition.begin(), True):
53 self.transitions.append(t.reversed())
54 return
55 # otherwise calculate transition and add it to out pool
56 transition.calculate(frames - 1)
57 self.transitions.append(transition)
58
59 def configure(cfg, composites, targets=None, fps=25):
60 """ generate all transitions configured in the INI-like configuration
61 string in <cfg> by using the given <composites> and return them
62 in a dictonary
63 """
64 def index(composite):
65 for i in range(len(targets)):
66 if composites[targets[i]].equals(composite, True):
67 return i
68 return None
69
70 # filter target composites from given composites
71 if not targets:
72 targets = Composites.targets(composites)
73 # prepare result
74 transitions = Transitions(targets,fps)
75
76 # walk through all items within the configuration string
77 for t_name, t in cfg:
78 # split animation time and composite sequence from t
79 time, sequence = t.split(',')
80 time = int(time)
81 # calculate frames needed for that animation time
82 frames = fps * float(time) / 1000.0
83 # split sequence list into key frames
84 sequence = [x.strip().lower() for x in sequence.split('/')]
85 for seq in parse_asterisk(sequence, targets):
86 if "*" in sequence:
87 name = "%s(%s)" % (t_name, "/".join(seq))
88 else:
89 name = t_name
90 # prepare list of key frame composites
91 transition = Transition(name)
92 try:
93 # walk trough composite sequence
94 for c_name in seq:
95 #c_name = c_name.lower()
96 if c_name[0] == '^':
97 # find a composite with that name
98 transition.append(composites[c_name[1:]].swapped())
99 else:
100 # find a composite with that name
101 transition.append(composites[c_name])
102 # log any failed find
103 except KeyError as err:
104 raise RuntimeError(
105 'composite "{}" could not be found in transition {}'.format(err, name))
106 transitions.add(transition,frames - 1)
107 log.debug("Loaded %d transitions from configuration.", len(transitions))
108 # return dictonary
109 return transitions
110
111 def solve(self, begin, end, flip):
112 log.debug("Solving transition %s(A,B) -> %s(%s)\n\t %s\n\t %s", begin.name, end.name, "B,A" if flip else "A,B", begin, end)
113 for transition in self.transitions:
114 # try to find original transition
115 if transition.begin().equals(begin, True) and transition.end().equals(end, True, flip):
116 log.debug("Solved #1 %s\n%s", transition.name(), transition)
117 return transition, False
118 if transition.begin().equals(begin, True, flip) and transition.end().equals(end, True):
119 log.debug("Solved #2 %s\n%s", transition.name(), transition)
120 return transition, True
121 # try reverse
122 if transition.begin().equals(end, True) and transition.end().equals(begin, True, flip):
123 log.debug("Solved #3 %s\n%s", transition.name(), transition)
124 return transition.reversed(), True
125 if transition.begin().equals(end, True, flip) and transition.end().equals(begin, True):
126 log.debug("Solved #4 %s\n%s", transition.name(), transition)
127 return transition.reversed(), False
128 return None, False
129
130 def travel(composites, previous=None):
131 """ return a list of pairs of composites along all possible transitions
132 between all given composites by walking the tree of all combinations
133 """
134 # if there is only one composite
135 if len(composites) == 1:
136 # transition to itself
137 return [composites[0], composites[0]]
138 # if call is not from recursion
139 if not previous:
140 # insert random first station
141 return Transitions.travel(composites, composites[0:1])
142 # if maximum length has been reached
143 if len(previous) == len(composites) * len(composites) + 1:
144 # return ready sequence
145 return previous
146 # for all composites
147 for a in composites:
148 # check if we haven't had that combination previously
149 if not is_in(previous, [previous[-1], a]):
150 # try that combination
151 r = Transitions.travel(composites, previous + [a])
152 # return result if we are ready here
153 if r:
154 return r
155 # no findings
156 return None
157
158 class Transition:
159
160 def __init__(self, name, a=None, b=None):
161 assert type(name) is str
162 self._name = name
163 if a:
164 # no overloaded constructors available in python m(
165 if b:
166 # got lists of frames in a and b with same length?
167 assert len(a) == len(b)
168 assert type(a[0]) is Frame
169 assert type(b[0]) is Frame
170 # rearrange composites
171 self.composites = [Composite("...", a[i], b[i])
172 for i in range(len(a))]
173 else:
174 # if we got only one list then it must be composites
175 assert type(a[0]) is Composite
176 self.composites = a
177 else:
178 self.composites = []
179 self.flip = None
180
181 def __str__(self):
182 def hidden( x, hidden ):
183 return str(x).replace(' ','_') if hidden else str(x)
184
185 # remember index when to flip sources A/B
186 result = "\t%s = %s -> %s:\n" % (self.name(),
187 self.begin().name, self.end().name)
188 # add table title
189 result += "\tNo. %s\n" % Composite.str_title()
190 # add composites until flipping point
191 for i in range(self.frames()):
192 if (not logKeyFramesOnly) or self.A(i).key:
193 result += (("\t%3d %s " + ("B%s\tA%s" if self.flip and i >= self.flip else "A%s\tB%s") + " %s\n") %
194 (i, " * " if self.A(i).key else " ",
195 hidden(self.A(i), self.A(i).invisible() or self.composites[i].covered()),
196 hidden(self.B(i), self.B(i).invisible()),
197 self.composites[i].name))
198 return result
199
200 def phi(self):
201 return self.begin().equals(self.end().swapped(), False)
202
203 def name(self):
204 if self.phi():
205 return "Φ(" + self._name + ")"
206 else:
207 return self._name
208
209 def append(self, composite):
210 assert type(composite) == Composite
211 self.composites.append(composite)
212
213 def frames(self): return len(self.composites)
214
215 def A(self, n=None):
216 if n is None:
217 return [c.A() for c in self.composites]
218 else:
219 assert type(n) is int
220 return self.composites[n].A()
221
222 def B(self, n=None):
223 if n is None:
224 return [c.B() for c in self.composites]
225 else:
226 assert type(n) is int
227 return self.composites[n].B()
228
229 def Az(self, z0, z1):
230 frames = []
231 for i,c in enumerate(self.composites):
232 if (not self.flip) or i < self.flip:
233 frames.append(c.Az(z0))
234 else:
235 frames.append(c.Az(z1))
236 return frames
237
238 def Bz(self, z0, z1):
239 frames = []
240 for i,c in enumerate(self.composites):
241 if (not self.flip) or i < self.flip:
242 frames.append(c.Bz(z0))
243 else:
244 frames.append(c.Bz(z1))
245 return frames
246
247 def begin(self): return self.composites[0]
248
249 def end(self): return self.composites[-1]
250
251 def reversed(self):
252 return Transition(self._name + "⁻¹", self.composites[::-1])
253
254 def swapped(self):
255 return Transition(swap_name(self._name), [c.swapped() for c in self.composites])
256
257 def calculate_flip(self):
258 """ find the first non overlapping rectangle pair within parameters and
259 return it's index
260 """
261 # check if a phi was applied
262 if self.phi():
263
264 # check if rectangle a and b overlap
265 def overlap(a, b):
266 return (a[L] < b[R] and a[R] > b[L] and a[T] < b[B] and a[B] > b[T])
267
268 # find the first non overlapping composite
269 for i in range(self.frames() - 2):
270 if not overlap(self.A(i).cropped(), self.B(i).cropped()):
271 return i
272 # at last we need to swap at the end
273 return self.frames() - 1
274 # no flipping
275 return None
276
277 def calculate(self, frames, a_corner=(R, T), b_corner=(L, T)):
278 """ calculate a transition between the given composites which shall
279 have the given amount of frames. Use a_corner of frames in A and
280 b_corner of frames in B to interpolate the animation movement.
281 """
282 if len(self.composites) != frames:
283 num_keys = len(self.keys())
284 if len(self.composites) != num_keys:
285 log.warning("Recalculating transition %s" % self.name())
286 self.composites = self.keys()
287 # calculate that transition and place it into the dictonary
288 log.debug("Calculating transition %s\t= %s \t(%s key frames)" %
289 (self.name(),
290 " / ".join([c.name for c in self.composites]),
291 num_keys))
292
293 # extract two lists of frames for use with interpolate()
294 a = [c.A() for c in self.composites]
295 b = [c.B() for c in self.composites]
296 # check if begin and end of animation are equal
297 if a[-1] == a[0] and b[-1] == b[0]:
298 # then swap the end composite
299 a[-1], b[-1] = b[-1], a[-1]
300 # generate animation
301 a = interpolate(a, frames, a_corner)
302 b = interpolate(b, frames, b_corner)
303 composites = []
304 j = 0
305 for i in range(len(a)):
306 if a[i].key:
307 name = self.composites[j].name
308 j += 1
309 else:
310 name = "..."
311 composites.append(Composite(len(composites), name, a[i], b[i]))
312 self.composites = composites
313 self.flip = self.calculate_flip()
314
315 def keys(self):
316 """ return the indices of all key composites
317 """
318 return [i for i in self.composites if i.key()]
319
320
321 def parse_asterisk(sequence, composites):
322 """ parses a string like '*/*' and returns all available variants with '*'
323 being replaced by composite names in 'composites'.
324 """
325 sequences = []
326 for k in range(len(sequence)):
327 if sequence[k] == '*':
328 for c in composites:
329 sequences += parse_asterisk(sequence[: k] +
330 [c.name] + sequence[k + 1:],
331 composites)
332 if not sequences:
333 sequences.append(sequence)
334 return sequences
335
336
337 def frange(x, y, jump):
338 """ like range() but for floating point values
339 """
340 while x < y:
341 yield x
342 x += jump
343
344
345 def bspline(points):
346 """ do a B - Spline interpolation between the given points
347 returns interpolated points
348 """
349 # parameter check
350 assert type(points) is np.ndarray
351 assert type(points[0]) is np.ndarray and len(points[0]) == 2
352 assert type(points[1]) is np.ndarray and len(points[1]) == 2
353 resolution = Transitions.resolution
354 # check if we have more than two points
355 if len(points) > 2:
356 # do interpolation
357 tck, u = spi.splprep(points.transpose(), s=0, k=2)
358 unew = np.arange(0, 1.0 + resolution, resolution)
359 return spi.splev(unew, tck)
360 elif len(points) == 2:
361 # throw points on direct line
362 x, y = [], []
363 for i in frange(0.0, 1.0 + resolution, resolution):
364 x.append(points[0][X] + (points[1][X] - points[0][X]) * i)
365 y.append(points[0][Y] + (points[1][Y] - points[0][Y]) * i)
366 return [np.array(x), np.array(y)]
367 else:
368 return None
369
370
371 def find_nearest(spline, points):
372 """ find indices in spline which are most near to the coordinates in points
373 """
374 nearest = []
375 for p in points:
376 # calculation lamba fn
377 distance = (spline[X] - p[X])**2 + (spline[Y] - p[Y])**2
378 # get index of point with the minimum distance
379 idx = np.where(distance == distance.min())
380 nearest.append(idx[0][0])
381 # return nearest points
382 return nearest
383
384
385 def measure(points):
386 """ measure distances between every given 2D point and the first point
387 """
388 positions = [(0, 0, 0)]
389 # enumerate between all points
390 for i in range(1, len(points)):
391 # calculate X/Y distances
392 dx = points[i][X] - points[i - 1][X]
393 dy = points[i][Y] - points[i - 1][Y]
394 # calculate movement speed V
395 dv = math.sqrt(dx**2 + dy**2)
396 # sum up to last position
397 dx = positions[-1][X] + abs(dx)
398 dy = positions[-1][Y] + abs(dy)
399 dv = positions[-1][V] + dv
400 # append to result
401 positions.append((dx, dy, dv))
402 # return array of distances
403 return positions
404
405
406 def smooth(x):
407 """ smooth value x by using a cosinus wave (0.0 <= x <= 1.0)
408 """
409 return (-math.cos(math.pi * x) + 1) / 2
410
411
412 def distribute(points, positions, begin, end, x0, x1, n):
413 """ from the sub set given by <points>[<begin>:<end>+1] selects <n> points
414 whose distances are smoothly distributed and returns them.
415 <poisitions> holds a list of distances between all <points> that will
416 be used for smoothing the distribution.
417 """
418 assert type(points) is np.ndarray
419 assert type(positions) is list
420 assert type(begin) is np.int64
421 assert type(end) is np.int64
422 assert type(x0) is float
423 assert type(x1) is float
424 assert type(n) is int
425 # calculate overall distance from begin to end
426 length = positions[end - 1][V] - positions[begin][V]
427 # begin result with the first point
428 result = []
429 # check if there is no movement
430 if length == 0.0:
431 for i in range(0, n):
432 result.append(points[begin])
433 else:
434 # calculate start points
435 pos0 = smooth(x0)
436 pos1 = smooth(x1)
437 for i in range(0, n):
438 # calculate current x
439 x = smooth(x0 + ((x1 - x0) / n) * i)
440 # calculate distance on curve from y0 to y
441 pos = (x - pos0) / (pos1 - pos0) * length + positions[begin][V]
442 # find point with that distance
443 for j in range(begin, end):
444 if positions[j][V] >= pos:
445 # append point to result
446 result.append(points[j])
447 break
448 # return result distribution
449 return result
450
451
452 def fade(begin, end, factor):
453 """ return value within begin and end at < factor > (0.0..1.0)
454 """
455 # check if we got a bunch of values to morph
456 if type(begin) in [list, tuple]:
457 result = []
458 # call fade() for every of these values
459 for i in range(len(begin)):
460 result.append(fade(begin[i], end[i], factor))
461 else:
462 # return the resulting float
463 result = begin + (end - begin) * factor
464 return result
465
466 def morph(begin, end, pt, corner, factor):
467 """ interpolates a new frame between two given frames 'begin and 'end'
468 putting the given 'corner' of the new frame's rectangle to point 'pt'.
469 'factor' is the position bewteen begin (0.0) and end (1.0).
470 """
471 result = Frame()
472 # calculate current size
473 size = fade(begin.size(), end.size(), factor)
474 # calculate current rectangle
475 result.rect = [pt[X] if corner[X] is L else int(round(pt[X] - size[X])),
476 pt[Y] if corner[Y] is T else int(round(pt[Y] - size[Y])),
477 pt[X] if corner[X] is R else int(round(pt[X] + size[X])),
478 pt[Y] if corner[Y] is B else int(round(pt[Y] + size[Y])),
479 ]
480 # calculate current alpha value and cropping
481 result.alpha = int(round(fade(begin.alpha, end.alpha, factor)))
482 result.crop = [int(round(x)) for x in fade(begin.crop, end.crop, factor)]
483 # copy orignial size from begin
484 result.original_size = begin.original_size
485 return result
486
487 def interpolate(key_frames, num_frames, corner):
488 """ interpolate < num_frames > points of one corner defined by < corner >
489 between the rectangles given by < key_frames >
490 """
491 # get corner points defined by index_x,index_y from rectangles
492 corners = np.array([i.corner(corner[X], corner[Y]) for i in key_frames])
493 # interpolate between corners and get the spline points and the indexes of
494 # those which are the nearest to the corner points
495 spline = bspline(corners)
496 # skip if we got no interpolation
497 if not spline:
498 return [], []
499 # find indices of the corner's nearest points within the spline
500 corner_indices = find_nearest(spline, corners)
501 # transpose point array
502 spline = np.transpose(spline)
503 # calulcate number of frames between every corner
504 num_frames_per_move = int(round(num_frames / (len(corner_indices) - 1)))
505 # measure the spline
506 positions = measure(spline)
507 # fill with point animation from corner to corner
508 animation = []
509 for i in range(1, len(corner_indices)):
510 # substitute indices of corner pair
511 begin = corner_indices[i - 1]
512 end = corner_indices[i]
513 # calculate range of X between 0.0 and 1.0 for these corners
514 _x0 = (i - 1) / (len(corner_indices) - 1)
515 _x1 = i / (len(corner_indices) - 1)
516 # create distribution of points between these corners
517 corner_animation = distribute(
518 spline, positions, begin, end, _x0, _x1, num_frames_per_move - 1)
519 # append first rectangle from parameters
520 animation.append(key_frames[i - 1])
521 # cound index
522 for j in range(len(corner_animation)):
523 # calculate current sinus wave acceleration
524 frame = morph(key_frames[i - 1], key_frames[i],
525 corner_animation[j], corner,
526 smooth(j / len(corner_animation)))
527 # append to resulting animation
528 animation.append(frame)
529 # append last rectangle from parameters
530 animation.append(key_frames[-1])
531 # return rectangle animation
532 return animation
533
534
535 def is_in(sequence, part):
536 """ returns true if 2-item list 'part' is in list 'sequence'
537 """
538 assert len(part) == 2
539 for i in range(0, len(sequence) - 1):
540 if sequence[i: i + 2] == part:
541 return True
542 return False
543
544 def fade_alpha(frame,alpha,frames):
545 result = []
546 for i in range(0,frames):
547 f = frame.duplicate()
548 f.alpha = fade(frame.alpha,alpha,smooth(float(i)/frames))
549 result.append(f)
550 return result
0 # Voctocore - The videomixer core-process
1
2 ## Design goals
3 Our Design is heavily influenced by gst-switch. We wanted a small videomixer core-process, whose sourcecode can be read and understand in about a weekend.
4 All Sources (Cameras, Slide-Grabbers) and Sinks (Streaming, Recording) should be separate Processes. As far as possible we wanted to reuse our existing and well-tested ffmpeg Commandlines for streaming and recording. It should be possible to connect additional Sinks at any time while the number of Sources is predefined in our Setup. All sources and sinks should be able to die and get restarted without taking the core process down.
5 While Sources and Sinks all run on the same Machine, Control- or Monitoring-Clients, for example a GUI, should be able to run on a different machine and connect to the core-process via Gigabit Ethernet. The core-process should be controllable by a very simple protocol which can easily be scripted or spoken with usual networking tools.
6
7 ## Design decisions
8 To meet our goal of "read and understand in about a weekend" python was chosen as language for the high-level parts, with [GStreamer](http://gstreamer.freedesktop.org/) for the low-level media handling. GStreamer can be controlled via the [PyGI](https://wiki.gnome.org/action/show/Projects/PyGObject) bindings from Python.
9 As an Idea borrowed from gst-switch, all Video- and Audio-Streams to and from the core are handled via TCP-Connections. Because they transport raw Video-Frames the only reasonable transport is via the loopback interface or a dedicated GBit-NIC (1920×1080×2 (UYVY)×8 (Bits)×25 (fps) = ~830 MBit/s). Nevertheless TCP is a quite efficient and good supported transport mechanism. For compatibility with ffmpeg and because of its good properties when streamed over TCP, [Matroska](http://www.matroska.org/) was chosen as a Container.
10
11 The ubiquitous Input/Output-Format into the core-process is therefore Raw UYVY Frames and Raw S16LE Audio in a Matroska container for Timestamping via TCP over localhost. Network handling is done in python, because it allows for greater flexibility. After the TCP connection is ready, its file descriptor is passed to GStreamer which handles the low-level read/write operations. To be able to attach/detach sinks, the `multifdsink`-Element can be used. For the Sources it's more complicated:
12
13 When a source is not connected, its video and audio stream must be substituted with black frames and silence, to that the remaining parts of the pipeline can keep on running. To achive this, a separate GStreamer-Pipeline is launched for an incoming Source-Connection and destroyed in case of a disconnect or an error. To pass Video -and Audio-Buffers between the Source-Pipelines and the other parts of the Mixer, we make use of the `inter(audio/video)(sink/source)`-Elements. `intervideosrc` and `interaudiosrc` implement the creation of black frames and silence, in case no source is connected or the source broke down somehow.
14
15 If enabled in Config, the core process offers two formats for most outputs: Raw-Frames in mkv as described above, which should be used to feed recording or streaming processes running on the same machine. For the GUI which usually runs on a different computer, they are not suited because of the bandwidth requirements (1920×1080 UYVY @25fps = 791 MBit/s). For this reason the Servers offers Preview-Ports for each Input and the Main-Mix, which serves the same content, but the video frames there are jpeg compressed, combined with uncompressed S16LE audio and encapsulated in mkv.
16
17 Also, if enabled in Config, another Building-Block is chained after the Main-Mix: the StreamBlanker. It is used in Cases when there should be no Stream, for example in Breaks between Talks. It is sourced from one ASource which usually accepts a Stream of Music-Loop and one or more VSources which usually accepts a "There is currently no Talk"-Loop. Because multiple VSources can be configured, one can additionally source a "We are not allowed to Stream this Talk" or any other Loop. All Video-Loops are combined with the Audio-Loop and can be selected from the GUI.
18
19 ## Block-Level Diagram
0 # 1. VOC2CORE
1
2 ## 1.1. Contents
3
4 <!-- TOC -->
5
6 - [1.1. Contents](#11-contents)
7 - [1.2. Purpose](#12-purpose)
8 - [1.3. Features](#13-features)
9 - [1.4. Installation](#14-installation)
10 - [1.4.1. Debian / Ubuntu](#141-debian--ubuntu)
11 - [1.4.2. Requirements](#142-requirements)
12 - [1.4.3. For vaapi en/decoding](#143-for-vaapi-endecoding)
13 - [1.4.4. Optional for the Example-Scripts](#144-optional-for-the-example-scripts)
14 - [1.5. Debugging](#15-debugging)
15 - [1.6. Mixing Pipeline](#16-mixing-pipeline)
16 - [1.6.1. Input Elements](#161-input-elements)
17 - [1.6.1.1. Sources](#1611-sources)
18 - [1.6.1.1.1. Test Sources](#16111-test-sources)
19 - [1.6.1.1.2. TCP Sources](#16112-tcp-sources)
20 - [1.6.1.1.3. File Sources](#16113-file-sources)
21 - [1.6.1.1.4. Decklink Sources](#16114-decklink-sources)
22 - [1.6.1.1.5. Image Sources](#16115-image-sources)
23 - [1.6.1.1.6. Video4Linux2 Sources](#16116-video4linux2-sources)
24 - [1.6.1.1.7. Common Source Attributes](#16117-common-source-attributes)
25 - [1.6.1.2. Background Video Source](#1612-background-video-source)
26 - [1.6.1.2.1. Multiple Background Video Sources (depending on Composite)](#16121-multiple-background-video-sources-depending-on-composite)
27 - [1.6.1.3. Blinding Sources (Video and Audio)](#1613-blinding-sources-video-and-audio)
28 - [1.6.1.3.1. A/V Blinding Source](#16131-av-blinding-source)
29 - [1.6.1.3.2. Separated Audio and Video Blinding Source](#16132-separated-audio-and-video-blinding-source)
30 - [1.6.1.4. Overlay Sources](#1614-overlay-sources)
31 - [1.6.1.4.1. Single Overlay Image File](#16141-single-overlay-image-file)
32 - [1.6.1.4.2. Multiple Overlay Image Files](#16142-multiple-overlay-image-files)
33 - [1.6.1.4.3. Select Overlays from a Schedule](#16143-select-overlays-from-a-schedule)
34 - [1.6.1.4.3.1. Filtering Events](#161431-filtering-events)
35 - [1.6.1.4.4. Additional Overlay Options](#16144-additional-overlay-options)
36 - [1.6.1.4.4.1. Auto-Off](#161441-auto-off)
37 - [1.6.2. Output Elements](#162-output-elements)
38 - [1.6.2.1. Mix Live](#1621-mix-live)
39 - [1.6.2.2. Mix Recording](#1622-mix-recording)
40 - [1.6.2.3. Mix Preview](#1623-mix-preview)
41 - [1.6.2.4. Sources Live](#1624-sources-live)
42 - [1.6.2.5. Sources Recording](#1625-sources-recording)
43 - [1.6.2.6. Sources Preview](#1626-sources-preview)
44 - [1.6.2.7. Mirror Ports](#1627-mirror-ports)
45 - [1.6.3. A/V Processing Elements](#163-av-processing-elements)
46 - [1.6.3.1. DeMux](#1631-demux)
47 - [1.6.3.2. Mux](#1632-mux)
48 - [1.6.4. Video Processing Elements](#164-video-processing-elements)
49 - [1.6.4.1. Scale](#1641-scale)
50 - [1.6.4.2. Mix Compositor](#1642-mix-compositor)
51 - [1.6.4.3. Mix Blinding Compositor](#1643-mix-blinding-compositor)
52 - [1.6.4.4. Sources Blinding Compositor](#1644-sources-blinding-compositor)
53 - [1.6.5. Audio Processing Elements](#165-audio-processing-elements)
54 - [1.6.5.1. Audio Mixer](#1651-audio-mixer)
55 - [1.6.5.2. Audio Blinding Mixer](#1652-audio-blinding-mixer)
56 - [1.6.6. Live Sources](#166-live-sources)
57 - [1.7. Decoder and Encoder](#17-decoder-and-encoder)
58 - [1.7.1. CPU](#171-cpu)
59 - [1.7.2. VAAPI](#172-vaapi)
60
61 <!-- /TOC -->
62
63 ## 1.2. Purpose
64
65 **VOC2CORE** is a server written in python which listens at port `9999` for incoming TCP connections to provide a command line interface to manipulate a [GStreamer](http://gstreamer.freedesktop.org/) pipeline it runs.
66 The gstreamer pipeline is meant to mix several incoming video and audio sources to different output sources.
67
68 Particularly it can be used to send mixtures of the incoming video and audio sources to a live audience and/or to a recording server.
69
70 **VOC2CORE** can be easily adapted to different scenarios by changing it's configuration.
71
72 One can use a simple terminal connection to control the mixing process or **VOC2GUI** which provides a visual interface that shows previews of all sources and the mixed output as well as a toolbar for all mixing commands.
73
74 ## 1.3. Features
75
76 **VOC2CORE** currently provides the following features:
77
78 * [Matroska](http://www.matroska.org/) enveloped A/V source input via TCP
79 * Image sources via URI
80 * [Decklink](https://www.blackmagicdesign.com/products/decklink) grabbed A/V sources
81 * Video4Linux2 video sources
82 * GStreamer generated test sources
83 * [Matroska](http://www.matroska.org/) enveloped A/V output via TCP
84 * Scaling of input sources to the desired output format
85 * Conversion of input formats to the desired output format
86 * Composition of video sources to a mixed video output (e.g. Picture in Picture)
87 * Blinding of mixed video and audio output (formerly known as "stream blanking", e.g. to interrupt live streaming between talks)
88 * Low resolution preview outputs of sources and mix for lower bandwidth monitoring
89 * High resolution outputs of sources and mix for high quality recording
90 * Remote controlling via command line interface
91 * Video transitions for fading any cuts
92 * Image overlays (e.g. for lower thirds)
93 * Reading a so-called [`schedule.xml`](https://github.com/voc/voctosched) which can provide meta data about the talks and that is used to address images individually for each talk that can be selected as overlay (e.g. speaker descriptions in lower thirds)
94 * Customization of video composites and transitions
95
96 ## 1.4. Installation
97
98 Currently voc2mix is only works on linux based operating systems. Currently its tested on ubuntu 18.04 and 19.10 as well
99 as debian buster. It will probably work on most linux distributions which can satisfy the dependencies below.
100 Voc2mix can run on Gstreamer version < 1.8 but at least 1.14 is recommended.
101
102 ### 1.4.1. Debian / Ubuntu
103
104 On Ubuntu 18.04 to 19.10 and Debian buster the following packages are needed. The python dependencies can also be handled in a venv.
105 Both Debian and Ubuntu provide voctomix packages which my or may not be outdated. Currently its recommended to check out voc2mix from the git repository.
106
107 ````bash
108 git clone https://github.com/voc/voctomix.git
109 git checkout voctomix2
20110 ````
21 17000… VSource** (Stream-Blanker) ---\
22 18000 ASource** (Stream-Blanker) ----\
23 \
24 16000 VSource (Background) \
25 \ \
26 --> VideoMix \
27 / \ -> StreamBlanker** -> StreamOutputPort** 15000
28 / \ /
29 / ------> OutputPort 11000
30 / / \-> Encoder* -> PreviewPort* 12000
31 / /
32 /----- -> AudioMix
33 /
34 10000… AVSource --> MirrorPort 13000…
35 \-> Encoder* -> PreviewPort* 14000…
36 \
37 \
38 \--> Slides -> SlidesStreamBlanker*** -> SlidesStreamOutputPort** 15001
39
40 9999 Control-Server
41 9998 GstNetTimeProvider Network-Clock
42
43 *) only when [previews] enabled=true is configured
44 **) only when [stream-blanker] enabled=true is configured
45 ***) only when [mix] slides_source_name=… is configured
111
112 ### 1.4.2. Requirements
113
114 ````bash
115 sudo apt install gstreamer1.0-plugins-bad gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-tools libgstreamer1.0-0 python3 python3-gi gir1.2-gstreamer-1.0 gir1.2-gst-plugins-base-1.0 python3-sdnotify python3-scipy
46116 ````
47117
48 ## Network Ports Listing
49 Ports that will accept Raw UYVY Frames and Raw S16LE Audio in a Matroska container:
50 - 10000, 10001, … – Main Video-Sources, depending on the number of configured Sources
51
52 Ports that will accept Raw UYVY Frames without Audio in a Matroska container:
53 - 16000 Mixer – Background Loop
54 - 17000, 17001, … – Stream-Blanker Video-Input, depending on the number of configured Stream-Blanker-Sources
55
56 Ports that will accept Raw S16LE Audio wihout Video in a Matroska container:
57 - 18000 – Stream-Blanker Audio-Input
58
59 Ports that will provide Raw UYVY Frames and Raw S16LE Audio in a Matroska container:
60 - 13000, 13001, … – Main Video-Source Mirrors, depending on the number of configured Sources
61 - 11000 – Main Mixer Output
62 - 15000 – Stream Output – only when [stream-blanker] enabled=true is configured
63
64 Ports that will provide JPEG Frames and Raw S16LE Audio in a Matroska container – only when [previews] enabled=true is configured
65 - 14000, 14001, … – Main Video-Source Mirrors, depending on the number of configured Sources
66 - 12000 – Main Mixer Output
67
68 Port 9999 will Accept Control Protocol Connections.
69
70 ## Control Protocol
71 To Control operation of the Video-Mixer, a simple line-based TCP-Protocol is used. The Video-Mixer accepts connection on TCP-Port 9999. The Control-Protocol is currently unstable and may change in any way at any given time. Regarding available Commands and their Reponses, the Code is the Documentation. There are 3 kinds of Messages:
72
73 ### 1. Commands from Client to Server
74 The Client may send Commands listed in the [Commands-File](./lib/commands.py). Each Command takes a number of Arguments which are separated by Space. There is currently no way to escape Spaces or Linebreaks in Arguments. A Command ends with a Unix-Linebreak.
75
76 There are two Kinds of Commands: `set_*` and `get_*`. `set`-Commands change the Mixer state while `get`-Commands dont. Both kinds of Commands are answered with the same Response-Message.
77
78 For example a `set_video_a cam1` Command could be respnded to with a `video_status cam1 cam2` Response-Message. A `get_video` Command will be answered with exactly the same Message.
79
80 ### 2. Errors in response to Commands
81 When a Command was invalid or had invalid Parameters, the Server responds with `error` followed by a Human Readable error message. A Machine-Readable error code is currently not available. The Error-Response always ends with a Unix Linebreak (The Message can not contain Linebreaks itself).
82
83 ### 3. Server Signals
84 When another Client issues a Command and the Server executed it successfully, the Server will signal this to all connected Clients. The Signal-Message format is identical to the Response-Format of the issued Command.
85
86 For example if Client `A` issued Command `set_video_a cam1`, Client `A`and Client `B` will both receive the same `video_status cam1 cam2` Response-Message.
87
88 ### Example Communication:
118 ### 1.4.3. For vaapi en/decoding
119
120 ````bash
121 sudo apt install gstreamer1.0-vaapi
89122 ````
90 < set_video_a cam1
91 > video_status cam1 cam2
92
93 < set_composite_mode side_by_side_equal
94 > composite_mode side_by_side_equal
95
96 < set_videos_and_composite grabber * fullscreen
97 > video_status grabber cam1
98 > composite_mode fullscreen
99
100 < get_video
101 > video_status cam2 cam1
102
103 < get_composite_mode
104 > composite_mode fullscreen
105
106 < set_video_a blafoo
107 > error unknown name foo
108
109 < get_stream_status
110 > stream_status live
111
112 < set_stream_blank pause
113 > stream_status blank pause
114
115 < set_stream_live
116 > stream_status live
117
118 … meanwhile in another control-server connection
119
120 > video_status cam1 cam2
121 > video_status cam2 cam1
122 > composite_mode side_by_side_equal
123 > composite_mode fullscreen
124 > stream_status blank pause
125 > stream_status live
126
123
124 ### 1.4.4. Optional for the Example-Scripts
125
126 ````bash
127 sudo apt install python3-pyinotify gstreamer1.0-libav rlwrap fbset ffmpeg netcat
127128 ````
128129
129 ### Messages
130 Messages are Client-to-Client information that don't change the Mixers state, while being distributed throuh its Control-Socket.
131
132 ````
133 < message cut bar moo
134 > message cut bar moo
135
136 … meanwhile in another control-server connection
137
138 > message cut bar moo
139 ````
140
141 They can be used to Implement Features like a "Cut-Button" in the GUI. When Clicked the GUI would emit a message to the Server which would distribute it to all Control-Clients. A recording Script may receive the notification and rotate its output-File.
142
143 ## Configuration
144 On Startup the Video-Mixer reads the following Configuration-Files:
145 - `<install-dir>/default-config.ini`
146 - `<install-dir>/config.ini`
147 - `/etc/voctomix/voctocore.ini`
148 - `/etc/voctocore.ini`
149 - `<homedir>/.voctocore.ini`
150 - `<File specified on Command-Line via --ini-file>`
151
152 From top to bottom the individual Settings override previous Settings. `default-config.ini` should not be edited, because a missing Setting will result in an Exception.
153
154 All Settings configured in the Server are available via the `get_config` Call on the Control-Port and will be used by the Clients, so there will be no need to duplicate Configuration options between Server and Clients.
155
156 ## Multi-Stream Audio Mixing
157 Voctomix has support for passing and mixing as many audio streams as desired. At the c3voc we use this feature for recording lectures with simultaneous translation. The number of streams is configured system-wide with the `[mix] audiostreams` setting which defaults to 1. All streams are always stereo. Setting it to 3 configures 3 stereo-streams.
158
159 Each tcp-feed for a camera (not stream-blanker and background-feeds) then needs to follow this channel layout (in this example: have 3 stereo-stream) or it will stall after the first couple seconds.
160
161 Similar all output-streams (mirrors, main-out, stream-out) will now present 3 stereo-streams. The streamblanker will correctly copy the blank-music to all streams when the stream-blanker is engaged.
162
163 For the internal decklink-sources, you have to configure the mapping in the source-section of the config:
164 ```
165 [mix]
166
167 audiostreams = 3
130 ## 1.5. Debugging
131
132 Here are some debugging tips:
133
134 * Use option `-v`, `-vv` or `-vvv` to set more and more verbose logging from **VOC2CORE**.
135 * Use option `-g`, `-gg` to set more and more verbose logging from GStreamer.
136 * Use option `-p` to generate file including string of pipeline that **VOC2CORE** is about to create.
137 * Use option `-d` to generate DOT graphs of the GStreamer pipeline that **VOC2CORE** has created.
138 * Use option `-D` to generate DOT graphs like with `-d` but set detail level of DOT graph.
139 * DOT graph files can be viewed with `xdot` for example.
140
141 ## 1.6. Mixing Pipeline
142
143 The following graph shows a simplified mixing pipeline.
144 The real GStreamer pipeline is much more complicated.
145 A so-called [DOT graph](https://www.graphviz.org/) of it can be generated by starting **VOC2CORE** with option `-d`. Those DOT graph files can be viewed with [xdot](https://github.com/jrfonseca/xdot.py) for example.
146
147 ![**VOC2CORE** Mixing Pipeline](images/pipelines.svg)
148
149 ### 1.6.1. Input Elements
150
151 #### 1.6.1.1. Sources
152
153 Live audio/video input can be delivered in different *kinds* via **TCP** in **Matroska format** or from a **Decklink capture card** source. **Video4Linux2** devices can also be used as video only sources. It is also possible to use **image** and **test** sources, but this mostly make sense for testing purposes.
154
155 All input sources must be named uniquely and listed in `mix/sources` within the configuration file:
156
157 ```ini
158 [mix]
159 sources = cam1,cam2
160 ```
161
162 Without any further configuration this will produce two test sources named `cam1` and `cam2`.
163
164 ##### 1.6.1.1.1. Test Sources
165
166 Without any further configuration a source becomes a **test source** by default.
167 Every test source will add a [videotestsrc](https://gstreamer.freedesktop.org/documentation/videotestsrc/index.html?gi-language=python) and an [audiotestsrc](https://gstreamer.freedesktop.org/documentation/audiotestsrc/index.html?gi-language=python) element to the internal GStreamer pipeline and so it produces a test video and sound.
168 As in the order they appear in `mix/sources` the test patterns of all test sources will iterate through the following values:
169
170 `smpte`, `ball`, `red`, `green`, `blue`, `black`, `white`, `checkers-1`, `checkers-2`, `checkers-4`, `checkers-8`, `circular`, `blink`, `smpte75`, `zone-plate`, `gamut`, `chroma-zone-plate`, `solid-color`, `smpte100`, `bar`, `snow`, `pinwheel`, `spokes`, `gradient`, `colors`
171
172 You can also set a specific audio pattern by setting `mix/wave` to one the following types:
173
174 `sine`, `square`, `saw`, `triangle`, `silence`, `white-noise`, `pink-noise`, `sine-table`, `ticks`, `gaussian-noise`, `red-noise`, `blue-noise`, `violet-noise`,
175
176 The default is `sine`, with a frequency of 1kHz at -18dbFS.
177
178 To set the pattern of a test source explicitly you need to add an own section `source.x` (where `x` is the source's identifier) to the configuration
179
180 ```ini
181 [mix]
182 sources = cam1,cam2
183
184 [source.cam1]
185 pattern = ball
186 wave = white-noise
187 ```
188
189 Now source `cam1` will show a moving white ball on black background instead of a *SMPTE* pattern signal and play white noise instead of a sine.
190
191 To change the *kind* of a source you need to set the `kind` attribute in the source's configuration section as described in the following paragraphs.
192
193 ##### 1.6.1.1.2. TCP Sources
194
195 You can use `tcp` as a source's `kind` if you would like to provide Matroska A/V streams via TCP.
196 **TCP sources** will be assigned to port `16000` and the following in the order in which they appear in `mix/sources`.
197
198 ```ini
199 [mix]
200 sources = cam1,cam2
201
202 [source.cam1]
203 kind = tcp
204
205 [source.cam2]
206 kind = tcp
207 ```
208
209 This configuration let VOC2CORE listen at port `16000` for an incoming TCP connection transporting a Matroska A/V stream for source `cam1` and at port `16001` for source `cam2`.
210
211 ##### 1.6.1.1.3. File Sources
212
213 You can use `file` as a source's `kind` if you would like to provide a file that will be played e.g. to provide a blinder animation. Setting the loop property to `false` is not useful at this point.
214
215 Currently, file sources are expected to be MPEG TS containers with MPEG-2 Video and MP2 or MP3 audio. Support of further container, audio and video types may be supported in future releases
216
217 ```ini
218 [source.blinder]
219 kind=file
220 location=/path/to/pause.ts
221 loop=true
222 ```
223
224 This configuration will loop pause.ts as the default blinder, using its audio and video
225
226 ##### 1.6.1.1.4. Decklink Sources
227
228 You can use `decklink` as a source's `kind` if you would like to grab audio and video from a [Decklink](https://www.blackmagicdesign.com/products/decklink) grabber card.
229
230 ```ini
231 [mix]
232 sources = cam1,cam2
168233
169234 [source.cam1]
170235 kind = decklink
171 devicenumber = 0
172 video_connection = SDI
173 video_mode = 1080p25
174 audio_connection = embedded
175
176 # Use audio from this camera
177 volume=1.0
178
179 # Map SDI-Channel 0 to the left ear and Channel 1 to the right ear of the Output-Stream 0
180 audiostream[0] = 0+1
236 devicenumber = 1
181237
182238 [source.cam2]
183239 kind = decklink
184 devicenumber = 1
185 video_connection = SDI
186 video_mode = 1080p25
187 audio_connection = embedded
188
189 # Use audio from this camera
190 volume=1.0
191
192 # Map SDI-Channel 0 to both ears ear of the Output-Stream 1
193 audiostream[1] = 0
194
195 # Map SDI-Channel 1 to both ears ear of the Output-Stream 2
196 audiostream[2] = 1
197 ```
198
199 With Audio-Embedders which can embed more then 2 Channels onto an SDI-Stream you can also fill all Streams from one SDI-Source. This requires at least GStreamer 1.12.3:
200 ```
201 [mix]
202
203 audiostreams = 3
240 devicenumber = 3
241 ```
242
243 You now have two **Decklink A/V grabber** sources at device number `1` for `cam1` and `3` for `cam2`.
244
245 Optional attributes of Decklink sources are:
246
247 | Attribute Name | Example Values | Default | Description (follow link)
248 | ------------------ | -------------------------------------------------- | --------- | -----------------------------------------
249 | `devicenumber` | `0`, `1`, `2`, ... | `0` | [Decklink `device-number`](https://gstreamer.freedesktop.org/documentation/decklink/decklinkvideosrc.html#decklinkvideosrc:device-number)
250 | `video_connection` | `auto`, `SDI`, `HDMI`, ... | `auto` | [Decklink `connection`](https://gstreamer.freedesktop.org/documentation/decklink/decklinkvideosrc.html#GstDecklinkConnection)
251 | `video_mode` | `auto`, `1080p25`, `1080i50`, ... | `auto` | [Decklink `modes`](https://gstreamer.freedesktop.org/documentation/decklink/decklinkvideosrc.html#decklinkvideosrc_GstDecklinkModes)
252 | `video_format` | `auto`, `8bit-YUV`, `10bit-YUV`, `8bit-ARGB`, ... | `auto` | [Decklink `video-format`](https://gstreamer.freedesktop.org/documentation/decklink/decklinkvideosrc.html#decklinkvideosrc_GstDecklinkVideoFormat)
253 | `audio_connection` | `auto`, `embedded`, `aes`, `analog`, `analog-xlr`, `analog-rca` | `auto` | [Decklink `audio-connection`](https://gstreamer.freedesktop.org/documentation/decklink/decklinkaudiosrc.html#GstDecklinkAudioConnection)
254
255 ##### 1.6.1.1.5. Image Sources
256
257 You can use `img` as a source's `kind` if you would like to generate a still video from an image file.
258
259 ```ini
260 [mix]
261 sources = cam1,cam2
204262
205263 [source.cam1]
206 kind = decklink
207 devicenumber = 0
208 video_connection = SDI
209 video_mode = 1080p25
210 audio_connection = embedded
211
212 # Use audio from this camera
213 volume=1.0
214
215 # Map SDI-Channel 0 to the left ear and Channel 1 to the right ear of the Output-Stream 0
216 audiostream[0] = 0+1
217 audiostream[1] = 2+3
218 audiostream[2] = 4+5
219 ```
264 kind = img
265 imguri = http://domain.com/image.jpg
266
267 [source.cam2]
268 kind = img
269 file = /opt/voctomix/image.png
270 ```
271
272 As you see you can use either `imguri` or `file` to select an image to use.
273
274 | Attribute Name | Example Values | Default | Description
275 | ------------------ | -------------------------------------------------- | --------- | -----------------------------------------
276 | `imguri` | `http://domain.com/image.jpg` | n/a | use image from URI
277 | `file` | `/opt/voctomix/image.png` | n/a | use image from local file
278
279 ##### 1.6.1.1.6. Video4Linux2 Sources
280
281 You can use `v4l2` as a source's `kind` if you would like to use video4linux2 devices as video input.
282 To get the supported video modes, resolution and framerate you can use ffprobe and ffplay.
283
284 ```bash
285 ffprobe /dev/video0
286 ```
287
288 ```ini
289 [mix]
290 sources = cam1,cam2
291
292 [source.cam1]
293 kind=v4l2
294 device=/dev/video2
295 width=1280
296 height=720
297 framerate=10/1
298 format=YUY2
299
300 ```
301
302 | Attribute Name | Example Values | Default | Description
303 | ------------------ | -------------------------------------------------- | ----------- | -----------------------------------------
304 | `device` | `/dev/video0` | /dev/video0 | video4linux2 device to use
305 | `width` | `1280` | 1920 | video width expected from the source
306 | `height` | `720` | 1080 | video height expected from the source
307 | `framerate` | `10/1` | 25/1 | video frame rate expected from the source
308 | `format` | `YUY2` | YUY2 | video format expected from the source
309
310 ##### 1.6.1.1.7. Common Source Attributes
311
312 These attributes can be set for all *kinds* of sources:
313
314 | Attribute Name | Example Values | Default | Description
315 | ------------------ | -------------------------------------------------- | ------------- | -----------------------------------------
316 | `scan` | `progressive`, `interlaced`, `psf` | `progressive` | select video mode (`psf` = Progressive segmented frame)
317 | `volume` | `0.0`, ..., `1.0` | `0.0` | audio volume (if reasonable)
318
319 #### 1.6.1.2. Background Video Source
320
321 The `background` source is *obligatory* and does not have to be listed in `mix/sources`.
322 The background source will be placed on bottom (z-order) of the video mix.
323 By default the background source is a `black` video test source.
324 Yout need to configure the background source (as any other) if you want to change that:
325
326 ```ini
327 [source.background]
328 kind=img
329 file=bg.png
330 ```
331
332 The background source is **video only** and so any audio sources will be ignored.
333
334 ##### 1.6.1.2.1. Multiple Background Video Sources (depending on Composite)
335
336 You may also have multiple backgrounds and attach them to your composites.
337 Often - for example - you have a logo in the background which needs to be shown on different places depending on where your composites leave unused space.
338
339 By configuring a list of background sources in `mix`/`backgrounds` you can configure every single one of them.
340 The default background source called `background` wont be used then.
341
342 ```ini
343 [mix]
344 backgrounds=background1,background2
345
346 [source.background1]
347 kind=img
348 file=bg.png
349 composites=fs
350
351 [source.background2]
352 kind=test
353 pattern=black
354 composites=sbs,pip
355 ```
356
357 To control when the backgrounds will be used just add a list of `composites` to your source configuration.
358 The core then will search for backgrounds that match the current composite and cut or fade them when composites are switched.
359
360 #### 1.6.1.3. Blinding Sources (Video and Audio)
361
362 The blinder (fka stream-blanker) blinds all live outputs.
363 You can activate the blinder in the configuration like that:
364
365 ```ini
366 [blinder]
367 enable=true
368 ```
369
370 By default the blinder generates a Gstreamer test source which shows a SMPTE pattern.
371 But you have several options to define your own blinder sources:
372
373 ##### 1.6.1.3.1. A/V Blinding Source
374
375 If you like to set up a custom blinding source you have to configure a source that is named `blinder`:
376
377 ```ini
378 [blinder]
379 enable=true
380
381 [source.blinder]
382 kind=test
383 pattern=black
384 volume=0.0
385 ```
386
387 This would define a blinder source that is a black screen with silent audio.
388 But you can use any other source kind too.
389
390 ##### 1.6.1.3.2. Separated Audio and Video Blinding Source
391
392 Another way to define binding sources is to configure one audio source and one or more video sources.
393 The blinder then will blind with the one given audio source but you can select between different video sources.
394 This is useful if you want to have different video messages which you want to differ (for different day times for example, like having a break at lunch or end of the event or a trouble message.
395 If you want to do so, you have to define the audio source within the blinding source and add as many video blinding sources within the `blinder` section:
396
397 ```ini
398 [blinder]
399 enable=true
400 videos=break,closed
401
402 [source.blinder]
403 kind=tcp
404
405 [source.break]
406 kind=tcp
407
408 [source.closed]
409 kind=tcp
410 ```
411
412 This will listen at three different ports for the audio source, the break video source and the closed video source.
413
414 #### 1.6.1.4. Overlay Sources
415
416 Overlays are placed on top (z-order) of the video mix.
417 Currently they can be provided as bitmap images only.
418
419 These bitmap images will be loaded from the current working directory.
420 If you want to specify an image directory you can use the attribute `overlay`/`path`:
421
422 ```ini
423 [overlay]
424 path = ./data/images/overlays
425 ```
426
427 Now all images will be loaded from the folder `./data/images/overlays`.
428
429 You can configure which overlay images will be available for an insertion in three different ways selectively or in parallel.
430
431 ##### 1.6.1.4.1. Single Overlay Image File
432
433 The simplest method is to set a single overlay file that will be displayed as overlay initially after the server starts:
434
435 ```ini
436 [overlay]
437 file = watermark.png|Watermark Sign
438 ```
439
440 The given file name can be followed by a `|` and a verbal description of the file's contents which substitutes the filename within selections in the user interface.
441
442 ##### 1.6.1.4.2. Multiple Overlay Image Files
443
444 You can also list multiple files which then can be selected between by using the property `files`:
445
446 ```ini
447 [overlay]
448 files = first.png|1st overlay, second.png|2nd overlay, third.png|3rd overlay
449 ```
450
451 Same principle but a comma separated list of image names and descriptions.
452 The `files` attribute will not activate an overlay at server start.
453
454 ##### 1.6.1.4.3. Select Overlays from a Schedule
455
456 A more complex method is to configure a schedule file which is an XML file including information about one or multiple event schedules.
457 From these information **VOC2MIX** can generate file names in the form of `event_{eid}_person_{pid}.png` or `event_{eid}_persons.png` where `{eid}` and `{pid}` are placeholders for the event/id and person ID of the speakers of the event.
458 The first variant is used to address every single speaker and the second variant all participating persons at once.
459
460 Below you can see an example consisting of the necessary XML elements and by that describing three events and up to three speakers.
461
462 ```xml
463 <?xml version='1.0' encoding='utf-8' ?>
464 <schedule>
465 <day>
466 <room>
467 <event id='1'>
468 <date>2019-01-01T10:00:00+02:00</date>
469 <duration>01:00</duration>
470 <room>HALL 1</room>
471 <title>Interesting talk in HALL 1 at 10:00</title>
472 <persons>
473 <person id='1'>Alice</person>
474 <person id='2'>Bob</person>
475 <person id='3'>Claire</person>
476 </persons>
477 </event>
478 <event id='2'>
479 <date>2019-01-01T10:00:00+02:00</date>
480 <duration>01:00</duration>
481 <room>HALL 2</room>
482 <title>Interesting talk in HALL 2 at 10:00</title>
483 <persons>
484 <person id='4'>Dick</person>
485 </persons>
486 </event>
487 <event id='3'>
488 <date>2019-01-01T11:15:00+02:00</date>
489 <duration>01:00</duration>
490 <room>HALL 2</room>
491 <title>Interesting talk in HALL 2 at 11:15</title>
492 <persons>
493 <person id='1'>Alice</person>
494 <person id='4'>Dick</person>
495 </persons>
496 </event>
497 </room>
498 </day>
499 </schedule>
500 ```
501
502 From this file **VOC2MIX** will generate the following file names (and descriptions) for which it will search:
503
504 ```txt
505 event_1_person_1.png|Alice
506 event_1_person_2.png|Bob
507 event_1_person_3.png|Claire
508 event_1_persons.png|Alice, Bob, Claire
509
510 event_2_person_4.png|Dick
511
512 event_3_person_1.png|Alice
513 event_3_person_4.png|Dick
514 event_3_persons.png|Alice, Dick
515 ```
516
517 **VOC2CORE** will present a list of all available (files present in file system) overlays if asked for.
518
519 ###### 1.6.1.4.3.1. Filtering Events
520
521 If you have multiple events in multiple rooms it might be of need to filter the current event which you are mixing.
522 The first filter criteria will always be the current time.
523 **VOC2MIX** automatically filters out events that are past or in future.
524
525 Additionally you might set the room ID to filter out all events which are not happening in the room you are mixing.
526
527 ```ini
528 [overlay]
529 schedule=schedule.xml
530 room=HALL 1
531 ```
532
533 Now **VOC2CORE** will list you the available overlay images only for room `HALL 1`.
534
535 ##### 1.6.1.4.4. Additional Overlay Options
536
537 ###### 1.6.1.4.4.1. Auto-Off
538
539 **VOC2GUI** presents a button called `auto-off` which can be switched on and off.
540 Selection a different insertion from the list or the change of the current composite will force to end a current insertion.
541 This is used to prevent uncomfortable visual effects.
542
543 ```ini
544 [overlay]
545 user-auto-off = true
546 auto-off = false
547 blend-time=300
548 ```
549
550 If `user-auto-off` is set the button can be switched by the user and it is present within the user interface of **VOC2GUI**.
551 `auto-off` sets the initial state of the auto-off feature.
552 And `blend-time` sets the duration of the in and out blending of overlays in milliseconds.
553
554 ### 1.6.2. Output Elements
555
556 #### 1.6.2.1. Mix Live
557
558 This is the mix that is intended as output for live streaming. It get blanked by the stream blanker when
559 the live button in the GUI is disabled.
560
561 #### 1.6.2.2. Mix Recording
562
563 This is the mix that is intended as output for recording. It is identical to the video displayed int the GUI as **MIX**.
564
565 #### 1.6.2.3. Mix Preview
566
567 This mix is intended for the **MIX** preview in the GUI.
568
569 #### 1.6.2.4. Sources Live
570
571 #### 1.6.2.5. Sources Recording
572
573 #### 1.6.2.6. Sources Preview
574
575 Source Preview elements are used to encode the different video streams which will be shown in the GUI.
576 If previews are not enabled the GUI will use the raw video mirror ports. This only can work if gui and core are running on the same machine.
577
578 ```ini
579 [previews]
580 enabled = true
581 live = true
582 vaapi=h264
583 videocaps=video/x-raw,width=1024,height=576,framerate=25/1
584 ```
585
586 | Attribute Name | Example Values | Default | Description
587 | ------------------ | ----------------------------------- | ----------- | -----------------------------------------
588 | `enable` | `true` | false | video4linux2 device to use
589 | `live` | `true` | false | video width expected from the source
590 | `vaapi` | `h264` | | h264, mpeg2 and jpeg are supported. If jpeg is used CPU decoding needs to be used ob the gui.
591 | `scale-method` | 2 | 0 | 0: Default scaling mode 1: Fast scaling mode, at the expense of quality 2: High quality scaling mode, at the expense of speed.
592 | `vaapi-denoise` | true | false | use VAAPI to denoise the video before encoding it
593
594 #### 1.6.2.7. Mirror Ports
595
596 Mirror ports provide a copy of the input stream of each source via an TCP port.
597
598 ```ini
599 [mirrors]
600 enabled=true
601 ```
602
603 | Attribute Name | Example Values | Default | Description
604 | ------------------ | ----------------------------------- | ----------- | -----------------------------------------
605 | `enable` | `true` | false |
606
607 ### 1.6.3. A/V Processing Elements
608
609 #### 1.6.3.1. DeMux
610
611 #### 1.6.3.2. Mux
612
613 ### 1.6.4. Video Processing Elements
614
615 #### 1.6.4.1. Scale
616
617 #### 1.6.4.2. Mix Compositor
618
619 #### 1.6.4.3. Mix Blinding Compositor
620
621 #### 1.6.4.4. Sources Blinding Compositor
622
623 ### 1.6.5. Audio Processing Elements
624
625 #### 1.6.5.1. Audio Mixer
626
627 #### 1.6.5.2. Audio Blinding Mixer
628
629 ### 1.6.6. Live Sources
630
631 If you want to expose sources (e.g. a slide grabber) as an additional output for recording and streaming purposes, use the `mix/livesources` directive, which takes a comma-separated list of sources to be exposed, like so:
632
633 ```ini
634 [mix]
635 sources=cam1,cam2,grabber
636 livesources=grabber
637 ```
638
639 This will expose the grabber on port 15001. If you specify further sources, they will appear on ports 15002, etc.
640
641 ## 1.7. Decoder and Encoder
642
643 Voc2mix needs to encoder and decode video on different place in the pipeline as well as in the GUI.
644 Encoding and decoding can consume much time of the CPU. Therefore this tasks can be offloaded to fixed function en-/decoder blocks.
645 Probably the most common architecture for this, at least on x86, is Intels VAAPI interface which is is not limited to intel GPUs.
646 Most Intel CPUs with build in GPU provide these functions with different feature sets.
647 As there is also a penalty one using these as the data needs to be up and downloaded to the GPU the impact of using a offloading in favor of
648 CPU en-/decoding differs depending an a number of variables.
649 Also the quality that can be expected from offloading differs on the hardware used.
650
651 ### 1.7.1. CPU
652
653 Voc2mix can use all software en-/decoder gstreamer provides. The current code offer h264, mpeg2 and jpeg.
654
655 ### 1.7.2. VAAPI
656
657 * <https://www.freedesktop.org/wiki/Software/vaapi/>
658 * <https://01.org/linuxmedia/vaapi>
659 * <https://en.wikipedia.org/wiki/Video_Acceleration_API>
660
661 To use VAAPI with voc2mix on intel GPUs at least a sandy bridge generation CPU is required.
662 Voc2mix can use the the vaapi encoder to encode the preview stream for the GUI.
663 The GUI it self can use VAAPI to decode the preview streams and also use VAAPI as video system do draw the video to the screen.
664 Both can significant reduce the CPU load.
665
666 En-/decoding with an NVIDIA GeForce 940MX also seems to work but there are issues when vaapi is also used as video system.
00 [mix]
1 videocaps = video/x-raw,format=I420,width=1920,height=1080,framerate=25/1,pixel-aspect-ratio=1/1,interlace-mode=progressive
2 audiocaps = audio/x-raw,format=S16LE,channels=2,layout=interleaved,rate=48000
3
4 ; tcp-ports will be 10000,10001,10002
5 sources = cam1,cam2,grabber
6
7 ; setting this will create another stream-blanker which can be used to stream slides with the blanker-feature
8 ;slides_source_name=grabber
9
10 ; number of stereo-streams used in the mix. the exact same number needs to be supplied from each
11 ; source and is passed to each sink
12 audiostreams = 1
13
14 ; set the initial audio source (shortcut for setting the volume of the
15 ; audio-sources to 1.0), defaults to the first source
16 ;audiosource = cam1
17
18 [source.cam1]
19 ;deinterlace = yes
20 ;deinterlace = no
21 deinterlace = assume-progressive
22 ;kind = decklink
23 ;devicenumber = 0
24 ;video_connection = SDI
25 ;video_mode = 1080i50
26 ;video_format = auto
27 ;audio_connection = embedded
28 ;volume=0.5
29
30 ;audiostream[0] = 0+1
31 ;audiostream[1] = 2
32 ;audiostream[2] = 3
33
34
35 [source.cam2]
36 ;deinterlace = yes
37 ;deinterlace = no
38 deinterlace = assume-progressive
39 ;kind = tcp
40 ;volume = 0.5
41
42 [source.grabber]
43 deinterlace = assume-progressive
44
45 ;[source.background]
46 ;kind = img
47 ;imguri = file:///opt/voc/share/background.png
48
49
50 [output-buffers]
51 ; voctocore has a buffer on all video-outputs, that store video-frames for your
52 ; sink when it can't handle them all in real-time. so if your sink takes 2 seconds
53 ; to process a really hard to precess frame, voctomix needs to store 50 frames
54 ; for you, hoping that your sink will catch up soon.
55 ; if the sink doesn ot catch up in time, voctomix will drop it and remove it
56 ; from the output. it's your task to restart it in such a situation.
57 ; by default, voctomix will store up to 500 frames for your sink (20 seconds)
58 ; you might want to up that even more for your recording-sink, so that it never
59 ; gets disconnected. for this reason, the following configuration raises the
60 ; default limit for the mix_out sink to a whopping 10'000 frames (400 seconds)
61 ;cam1_mirror = 500
62 ;cam2_mirror = 500
63 ;grabber_mirror = 500
64 mix_out = 10000
65 ;streamblanker_out = 500
66
67 [fullscreen]
68 ; if configured, switching to fullscreen will automatically select this
69 ; source. if not configured, it will not change the last set source
70 ;default-a = cam1
71
72 [side-by-side-equal]
73 ; defaults to 1% of the video width
74 ;border = 0
75 ;gutter = 12
76 ;atop = 50
77 ;btop = 200
78
79 ; if configured, switching to the sbs-equal mode will automatically select these
80 ; sources. if not configured, it will not change the last set sources
81 ;default-a = cam1
82 ;default-b = cam2
83
84 [side-by-side-preview]
85 ;asize = 1024x576
86 ;acrop=0/0/0/0
87 ;apos = 12/12
88 ;bsize = 320x180
89 ;bcrop=0/640/0/640
90 ;bpos = 948/528
91
92 ; automatically select these sources when switching to sbs-preview
93 ;default-a = grabber
94 ;default-b = cam1
95
96 [picture-in-picture]
97 ;pipsize = 320x180
98 ;pipcrop=0/600/0/600
99 ;pippos = 948/528
100
101 ; automatically select these sources when switching to pip
102 ;default-a = grabber
103 ;default-b = cam1
1 sources = CAM1,CAM2,LAPTOP
1042
1053 [previews]
106 ; disable if ui & server run on the same computer and can exchange uncompressed video frames
107 enabled = false
108 deinterlace = false
4 ; enable previews so we can see something in VOC2GUI
5 enabled = true
1096
110 ; use vaapi to encode the previews, can be h264, mpeg2 or jpeg (BUT ONLY h264 IS TESTED)
111 ; not all encoders are available on all CPUs
112 ;vaapi = h264
7 [composites]
8 ; fullscreen source B is full transparent
9 FULL.alpha-b = 0
11310
114 ; default to mix-videocaps, only applicable if enabled=true
115 ; you can change the framerate and the width/height, but nothing else
116 ;videocaps = video/x-raw,width=1024,height=576,framerate=25/1
117
118 [stream-blanker]
119 enabled = true
120 sources = pause,nostream
121 volume = 1.0
122
123 ;[source.stream-blanker-pause]
124 ;kind=img
125 ;imguri=file:///home/peter/VOC/voctomix/bgloop.jpg
126
127 [mirrors]
128 ; disable if not needed
129 enabled = true
11 [transitions]
12 ; unique name = ms, from / [... /] to
13 FADE = 750, FULL / FULL
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
2 <!--Created by yEd 3.19-->
3 <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
4 <key for="port" id="d1" yfiles.type="portgraphics"/>
5 <key for="port" id="d2" yfiles.type="portgeometry"/>
6 <key for="port" id="d3" yfiles.type="portuserdata"/>
7 <key attr.name="url" attr.type="string" for="node" id="d4"/>
8 <key attr.name="description" attr.type="string" for="node" id="d5"/>
9 <key for="node" id="d6" yfiles.type="nodegraphics"/>
10 <key for="graphml" id="d7" yfiles.type="resources"/>
11 <key attr.name="url" attr.type="string" for="edge" id="d8"/>
12 <key attr.name="description" attr.type="string" for="edge" id="d9"/>
13 <key for="edge" id="d10" yfiles.type="edgegraphics"/>
14 <graph edgedefault="directed" id="G">
15 <data key="d0" xml:space="preserve"/>
16 <node id="n0">
17 <data key="d6">
18 <y:ShapeNode>
19 <y:Geometry height="50.0" width="102.0" x="775.3542540472317" y="344.20048001918025"/>
20 <y:Fill color="#996600" transparent="false"/>
21 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
22 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="49.0" y="23.0">
23 <y:LabelModel>
24 <y:SmartNodeLabelModel distance="4.0"/>
25 </y:LabelModel>
26 <y:ModelParameter>
27 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
28 </y:ModelParameter>
29 </y:NodeLabel>
30 <y:Shape type="roundrectangle"/>
31 </y:ShapeNode>
32 </data>
33 </node>
34 <node id="n1">
35 <data key="d6">
36 <y:ShapeNode>
37 <y:Geometry height="50.0" width="102.0" x="771.1969040701439" y="339.32117751882834"/>
38 <y:Fill color="#CC9900" transparent="false"/>
39 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
40 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="49.0" y="23.0">
41 <y:LabelModel>
42 <y:SmartNodeLabelModel distance="4.0"/>
43 </y:LabelModel>
44 <y:ModelParameter>
45 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
46 </y:ModelParameter>
47 </y:NodeLabel>
48 <y:Shape type="roundrectangle"/>
49 </y:ShapeNode>
50 </data>
51 </node>
52 <node id="n2">
53 <data key="d6">
54 <y:ShapeNode>
55 <y:Geometry height="37.68809849521194" width="60.59999999999991" x="139.04320000000013" y="484.11528624901007"/>
56 <y:Fill color="#996600" transparent="false"/>
57 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
58 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="28.299999999999955" y="16.844049247605994">
59 <y:LabelModel>
60 <y:SmartNodeLabelModel distance="4.0"/>
61 </y:LabelModel>
62 <y:ModelParameter>
63 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
64 </y:ModelParameter>
65 </y:NodeLabel>
66 <y:Shape type="roundrectangle"/>
67 </y:ShapeNode>
68 </data>
69 </node>
70 <node id="n3">
71 <data key="d6">
72 <y:ShapeNode>
73 <y:Geometry height="324.131942037416" width="63.0" x="286.1807808" y="308.1929806592001"/>
74 <y:Fill color="#996600" transparent="false"/>
75 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
76 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="29.5" y="160.065971018708">
77 <y:LabelModel>
78 <y:SmartNodeLabelModel distance="4.0"/>
79 </y:LabelModel>
80 <y:ModelParameter>
81 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
82 </y:ModelParameter>
83 </y:NodeLabel>
84 <y:Shape type="roundrectangle"/>
85 </y:ShapeNode>
86 </data>
87 </node>
88 <node id="n4">
89 <data key="d6">
90 <y:ShapeNode>
91 <y:Geometry height="324.131942037416" width="63.0" x="281.122848" y="302.27827839999986"/>
92 <y:Fill color="#CC9900" transparent="false"/>
93 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
94 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="29.5" y="160.065971018708">
95 <y:LabelModel>
96 <y:SmartNodeLabelModel distance="4.0"/>
97 </y:LabelModel>
98 <y:ModelParameter>
99 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
100 </y:ModelParameter>
101 </y:NodeLabel>
102 <y:Shape type="roundrectangle"/>
103 </y:ShapeNode>
104 </data>
105 </node>
106 <node id="n5">
107 <data key="d6">
108 <y:ShapeNode>
109 <y:Geometry height="169.5" width="102.0" x="399.0" y="173.4999999999999"/>
110 <y:Fill color="#FFCC00" transparent="false"/>
111 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
112 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.935546875" x="14.5322265625" xml:space="preserve" y="68.78125">Mix
113 Compositor<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
114 <y:Shape type="roundrectangle"/>
115 </y:ShapeNode>
116 </data>
117 </node>
118 <node id="n6">
119 <data key="d6">
120 <y:ShapeNode>
121 <y:Geometry height="50.0" width="102.0" x="399.0" y="570.671942037416"/>
122 <y:Fill color="#FFCC00" transparent="false"/>
123 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
124 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="38.107421875" x="31.9462890625" xml:space="preserve" y="9.03125">Audio
125 Mixer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
126 <y:Shape type="roundrectangle"/>
127 </y:ShapeNode>
128 </data>
129 </node>
130 <node id="n7">
131 <data key="d6">
132 <y:ShapeNode>
133 <y:Geometry height="324.131942037416" width="63.0" x="276.0" y="296.53999999999996"/>
134 <y:Fill color="#FFCC00" transparent="false"/>
135 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
136 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" rotationAngle="270.0" textColor="#000000" verticalTextPosition="bottom" visible="true" width="45.68359375" x="22.515625" xml:space="preserve" y="139.224174143708">DeMux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
137 <y:Shape type="roundrectangle"/>
138 </y:ShapeNode>
139 </data>
140 </node>
141 <node id="n8">
142 <data key="d6">
143 <y:ShapeNode>
144 <y:Geometry height="50.0" width="102.0" x="656.75" y="260.01999999999987"/>
145 <y:Fill color="#FFCC00" transparent="false"/>
146 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
147 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.935546875" x="14.5322265625" xml:space="preserve" y="2.046875">Mix
148 Blinding
149 Compositor<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
150 <y:Shape type="roundrectangle"/>
151 </y:ShapeNode>
152 </data>
153 </node>
154 <node id="n9">
155 <data key="d6">
156 <y:ShapeNode>
157 <y:Geometry height="50.0" width="131.0" x="92.40000000000009" y="243.75711999999987"/>
158 <y:Fill color="#CCFFCC" transparent="false"/>
159 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
160 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.177734375" x="23.9111328125" xml:space="preserve" y="2.046875">Background
161 Video Source
162 :16000<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
163 <y:Shape type="fatarrow"/>
164 </y:ShapeNode>
165 </data>
166 </node>
167 <node id="n10">
168 <data key="d6">
169 <y:ShapeNode>
170 <y:Geometry height="50.0" width="102.0" x="656.75" y="570.671942037416"/>
171 <y:Fill color="#FFCC00" transparent="false"/>
172 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
173 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="52.6796875" x="24.66015625" xml:space="preserve" y="2.046875">Audio
174 Blinding
175 Mixer<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
176 <y:Shape type="roundrectangle"/>
177 </y:ShapeNode>
178 </data>
179 </node>
180 <node id="n11">
181 <data key="d6">
182 <y:ShapeNode>
183 <y:Geometry height="37.0" width="37.0" x="689.25" y="450.21194203741595"/>
184 <y:Fill color="#FFCC00" transparent="false"/>
185 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
186 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="3.9697265625" xml:space="preserve" y="9.515625">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
187 <y:Shape type="ellipse"/>
188 </y:ShapeNode>
189 </data>
190 </node>
191 <node id="n12">
192 <data key="d6">
193 <y:ShapeNode>
194 <y:Geometry height="50.0" width="131.0" x="869.75" y="443.71194203741595"/>
195 <y:Fill color="#99CCFF" transparent="false"/>
196 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
197 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.216796875" x="42.3916015625" xml:space="preserve" y="2.046875">Mix
198 Live
199 :15000<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
200 <y:Shape type="fatarrow"/>
201 </y:ShapeNode>
202 </data>
203 </node>
204 <node id="n13">
205 <data key="d6">
206 <y:ShapeNode>
207 <y:Geometry height="50.0" width="131.0" x="525.6528" y="441.3278927898099"/>
208 <y:Fill color="#99CCFF" transparent="false"/>
209 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
210 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.185546875" x="39.9072265625" xml:space="preserve" y="2.046875">Mix
211 Preview
212 :11100<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
213 <y:Shape type="fatarrow"/>
214 </y:ShapeNode>
215 </data>
216 </node>
217 <node id="n14">
218 <data key="d6">
219 <y:ShapeNode>
220 <y:Geometry height="37.0" width="37.0" x="461.5" y="447.8278927898099"/>
221 <y:Fill color="#FFCC00" transparent="false"/>
222 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
223 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="3.9697265625" xml:space="preserve" y="9.515625">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
224 <y:Shape type="ellipse"/>
225 </y:ShapeNode>
226 </data>
227 </node>
228 <node id="n15">
229 <data key="d6">
230 <y:ShapeNode>
231 <y:Geometry height="50.0" width="131.0" x="525.6528" y="632.6737719544125"/>
232 <y:Fill color="#CCFFCC" transparent="false"/>
233 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
234 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.400390625" x="23.7998046875" xml:space="preserve" y="2.046875">Blinding
235 Audio Source
236 :18000
237 <y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
238 <y:Shape type="fatarrow"/>
239 </y:ShapeNode>
240 </data>
241 </node>
242 <node id="n16">
243 <data key="d6">
244 <y:ShapeNode>
245 <y:Geometry height="50.0" width="131.0" x="875.512533632" y="692.1179378714093"/>
246 <y:Fill color="#0066CC" transparent="false"/>
247 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
248 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
249 <y:LabelModel>
250 <y:SmartNodeLabelModel distance="4.0"/>
251 </y:LabelModel>
252 <y:ModelParameter>
253 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
254 </y:ModelParameter>
255 </y:NodeLabel>
256 <y:Shape type="fatarrow"/>
257 </y:ShapeNode>
258 </data>
259 </node>
260 <node id="n17">
261 <data key="d6">
262 <y:ShapeNode>
263 <y:Geometry height="50.0" width="131.0" x="872.7694588159999" y="686.5857706010091"/>
264 <y:Fill color="#6699FF" transparent="false"/>
265 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
266 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
267 <y:LabelModel>
268 <y:SmartNodeLabelModel distance="4.0"/>
269 </y:LabelModel>
270 <y:ModelParameter>
271 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
272 </y:ModelParameter>
273 </y:NodeLabel>
274 <y:Shape type="fatarrow"/>
275 </y:ShapeNode>
276 </data>
277 </node>
278 <node id="n18">
279 <data key="d6">
280 <y:ShapeNode>
281 <y:Geometry height="50.0" width="131.0" x="869.7499999999999" y="680.946289871409"/>
282 <y:Fill color="#99CCFF" transparent="false"/>
283 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
284 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.767578125" x="33.1162109375" xml:space="preserve" y="2.046875">Sources
285 Recording
286 :13000...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
287 <y:Shape type="fatarrow"/>
288 </y:ShapeNode>
289 </data>
290 </node>
291 <node id="n19">
292 <data key="d6">
293 <y:ShapeNode>
294 <y:Geometry height="50.0" width="131.0" x="525.6528" y="503.94194203741597"/>
295 <y:Fill color="#99CCFF" transparent="false"/>
296 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
297 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.767578125" x="33.1162109375" xml:space="preserve" y="2.046875">Mix
298 Recording
299 :11000<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
300 <y:Shape type="fatarrow"/>
301 </y:ShapeNode>
302 </data>
303 </node>
304 <node id="n20">
305 <data key="d6">
306 <y:ShapeNode>
307 <y:Geometry height="37.0" width="37.0" x="401.5" y="510.44194203741597"/>
308 <y:Fill color="#FFCC00" transparent="false"/>
309 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
310 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="3.9697265625" xml:space="preserve" y="9.515625">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
311 <y:Shape type="ellipse"/>
312 </y:ShapeNode>
313 </data>
314 </node>
315 <node id="n21">
316 <data key="d6">
317 <y:ShapeNode>
318 <y:Geometry height="37.0" width="37.0" x="149.92569600000007" y="583.2604437078156"/>
319 <y:Fill color="#996600" transparent="false"/>
320 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
321 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="16.5" y="16.5">
322 <y:LabelModel>
323 <y:SmartNodeLabelModel distance="4.0"/>
324 </y:LabelModel>
325 <y:ModelParameter>
326 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
327 </y:ModelParameter>
328 </y:NodeLabel>
329 <y:Shape type="ellipse"/>
330 </y:ShapeNode>
331 </data>
332 </node>
333 <node id="n22">
334 <data key="d6">
335 <y:ShapeNode>
336 <y:Geometry height="37.0" width="37.0" x="145.9192000000001" y="579.402848"/>
337 <y:Fill color="#CC9900" transparent="false"/>
338 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
339 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="16.5" y="16.5">
340 <y:LabelModel>
341 <y:SmartNodeLabelModel distance="4.0"/>
342 </y:LabelModel>
343 <y:ModelParameter>
344 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
345 </y:ModelParameter>
346 </y:NodeLabel>
347 <y:Shape type="ellipse"/>
348 </y:ShapeNode>
349 </data>
350 </node>
351 <node id="n23">
352 <data key="d6">
353 <y:ShapeNode>
354 <y:Geometry height="37.0" width="37.0" x="142.04320000000007" y="575.0834453078159"/>
355 <y:Fill color="#FFCC00" transparent="false"/>
356 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
357 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="3.9697265625" xml:space="preserve" y="9.515625">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
358 <y:Shape type="ellipse"/>
359 </y:ShapeNode>
360 </data>
361 </node>
362 <node id="n24">
363 <data key="d6">
364 <y:ShapeNode>
365 <y:Geometry height="30.0" width="7.0" x="281.73759999999993" y="773.1599999999999"/>
366 <y:Fill hasColor="false" transparent="false"/>
367 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
368 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="1.5" y="13.0">
369 <y:LabelModel>
370 <y:SmartNodeLabelModel distance="4.0"/>
371 </y:LabelModel>
372 <y:ModelParameter>
373 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
374 </y:ModelParameter>
375 </y:NodeLabel>
376 <y:Shape type="rectangle"/>
377 </y:ShapeNode>
378 </data>
379 </node>
380 <node id="n25">
381 <data key="d6">
382 <y:ShapeNode>
383 <y:Geometry height="30.0" width="7.0" x="326.65279999999996" y="773.1599999999999"/>
384 <y:Fill hasColor="false" transparent="false"/>
385 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
386 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="1.5" y="13.0">
387 <y:LabelModel>
388 <y:SmartNodeLabelModel distance="4.0"/>
389 </y:LabelModel>
390 <y:ModelParameter>
391 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
392 </y:ModelParameter>
393 </y:NodeLabel>
394 <y:Shape type="rectangle"/>
395 </y:ShapeNode>
396 </data>
397 </node>
398 <node id="n26">
399 <data key="d6">
400 <y:ShapeNode>
401 <y:Geometry height="30.0" width="7.0" x="346.068" y="773.1599999999999"/>
402 <y:Fill hasColor="false" transparent="false"/>
403 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
404 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="1.5" y="13.0">
405 <y:LabelModel>
406 <y:SmartNodeLabelModel distance="4.0"/>
407 </y:LabelModel>
408 <y:ModelParameter>
409 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
410 </y:ModelParameter>
411 </y:NodeLabel>
412 <y:Shape type="rectangle"/>
413 </y:ShapeNode>
414 </data>
415 </node>
416 <node id="n27">
417 <data key="d6">
418 <y:ShapeNode>
419 <y:Geometry height="30.0" width="7.0" x="390.9832" y="773.1599999999999"/>
420 <y:Fill hasColor="false" transparent="false"/>
421 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
422 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="1.5" y="13.0">
423 <y:LabelModel>
424 <y:SmartNodeLabelModel distance="4.0"/>
425 </y:LabelModel>
426 <y:ModelParameter>
427 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
428 </y:ModelParameter>
429 </y:NodeLabel>
430 <y:Shape type="rectangle"/>
431 </y:ShapeNode>
432 </data>
433 </node>
434 <node id="n28">
435 <data key="d6">
436 <y:ShapeNode>
437 <y:Geometry height="30.0" width="7.0" x="413.6616" y="773.1599999999999"/>
438 <y:Fill hasColor="false" transparent="false"/>
439 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
440 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="1.5" y="13.0">
441 <y:LabelModel>
442 <y:SmartNodeLabelModel distance="4.0"/>
443 </y:LabelModel>
444 <y:ModelParameter>
445 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
446 </y:ModelParameter>
447 </y:NodeLabel>
448 <y:Shape type="rectangle"/>
449 </y:ShapeNode>
450 </data>
451 </node>
452 <node id="n29">
453 <data key="d6">
454 <y:ShapeNode>
455 <y:Geometry height="30.0" width="7.0" x="458.57680000000005" y="773.1599999999999"/>
456 <y:Fill hasColor="false" transparent="false"/>
457 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
458 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="1.5" y="13.0">
459 <y:LabelModel>
460 <y:SmartNodeLabelModel distance="4.0"/>
461 </y:LabelModel>
462 <y:ModelParameter>
463 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
464 </y:ModelParameter>
465 </y:NodeLabel>
466 <y:Shape type="rectangle"/>
467 </y:ShapeNode>
468 </data>
469 </node>
470 <node id="n30">
471 <data key="d6">
472 <y:ShapeNode>
473 <y:Geometry height="30.0" width="7.0" x="485.15279999999996" y="773.1599999999999"/>
474 <y:Fill hasColor="false" transparent="false"/>
475 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
476 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="1.5" y="13.0">
477 <y:LabelModel>
478 <y:SmartNodeLabelModel distance="4.0"/>
479 </y:LabelModel>
480 <y:ModelParameter>
481 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
482 </y:ModelParameter>
483 </y:NodeLabel>
484 <y:Shape type="rectangle"/>
485 </y:ShapeNode>
486 </data>
487 </node>
488 <node id="n31">
489 <data key="d6">
490 <y:ShapeNode>
491 <y:Geometry height="30.0" width="7.0" x="530.068" y="773.1599999999999"/>
492 <y:Fill hasColor="false" transparent="false"/>
493 <y:BorderStyle hasColor="false" raised="false" type="line" width="1.0"/>
494 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="1.5" y="13.0">
495 <y:LabelModel>
496 <y:SmartNodeLabelModel distance="4.0"/>
497 </y:LabelModel>
498 <y:ModelParameter>
499 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
500 </y:ModelParameter>
501 </y:NodeLabel>
502 <y:Shape type="rectangle"/>
503 </y:ShapeNode>
504 </data>
505 </node>
506 <node id="n32">
507 <data key="d6">
508 <y:ShapeNode>
509 <y:Geometry height="21.840000000000146" width="60.59999999999991" x="560.8528" y="773.1599999999999"/>
510 <y:Fill color="#CCFFCC" transparent="false"/>
511 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
512 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="35.072265625" x="12.763867187499955" xml:space="preserve" y="1.9356250000000728">Input<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
513 <y:Shape type="fatarrow"/>
514 </y:ShapeNode>
515 </data>
516 </node>
517 <node id="n33">
518 <data key="d6">
519 <y:ShapeNode>
520 <y:Geometry height="21.840000000000146" width="60.59999999999991" x="639.6827999999999" y="773.1599999999999"/>
521 <y:Fill color="#99CCFF" transparent="false"/>
522 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
523 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="45.68359375" x="7.4582031249999545" xml:space="preserve" y="1.9356250000000728">Output<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
524 <y:Shape type="fatarrow"/>
525 </y:ShapeNode>
526 </data>
527 </node>
528 <node id="n34">
529 <data key="d6">
530 <y:ShapeNode>
531 <y:Geometry height="37.68809849521194" width="60.59999999999985" x="449.70000000000005" y="380.13979429459795"/>
532 <y:Fill color="#FFCC00" transparent="false"/>
533 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
534 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="36.28515625" x="12.157421874999955" xml:space="preserve" y="9.859674247605994">Scale<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
535 <y:Shape type="roundrectangle"/>
536 </y:ShapeNode>
537 </data>
538 </node>
539 <node id="n35">
540 <data key="d6">
541 <y:ShapeNode>
542 <y:Geometry height="50.0" width="102.0" x="766.53392" y="334.37099449521315"/>
543 <y:Fill color="#FFCC00" transparent="false"/>
544 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
545 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="72.935546875" x="14.5322265625" xml:space="preserve" y="2.046875">Sources
546 Blinding
547 Compositor<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
548 <y:Shape type="roundrectangle"/>
549 </y:ShapeNode>
550 </data>
551 </node>
552 <node id="n36">
553 <data key="d6">
554 <y:ShapeNode>
555 <y:Geometry height="30.0" width="30.0" x="563.875" y="344.37099449521315"/>
556 <y:Fill color="#FFCC00" transparent="false"/>
557 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
558 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="sides" modelPosition="n" textColor="#000000" verticalTextPosition="bottom" visible="true" width="75.44921875" x="-22.724609375" xml:space="preserve" y="-21.96875">live source?</y:NodeLabel>
559 <y:Shape type="diamond"/>
560 </y:ShapeNode>
561 </data>
562 </node>
563 <node id="n37">
564 <data key="d6">
565 <y:ShapeNode>
566 <y:Geometry height="30.0" width="30.0" x="730.2827999999998" y="765.0"/>
567 <y:Fill color="#FFCC00" transparent="false"/>
568 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
569 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="sides" modelPosition="n" textColor="#000000" verticalTextPosition="bottom" visible="true" width="31.9140625" x="-0.95703125" xml:space="preserve" y="-21.96875">filter</y:NodeLabel>
570 <y:Shape type="diamond"/>
571 </y:ShapeNode>
572 </data>
573 </node>
574 <node id="n38">
575 <data key="d6">
576 <y:ShapeNode>
577 <y:Geometry height="37.68809849521194" width="60.59999999999985" x="134.7488000000002" y="479.37608624901026"/>
578 <y:Fill color="#CC9900" transparent="false"/>
579 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
580 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="28.299999999999926" y="16.844049247605994">
581 <y:LabelModel>
582 <y:SmartNodeLabelModel distance="4.0"/>
583 </y:LabelModel>
584 <y:ModelParameter>
585 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
586 </y:ModelParameter>
587 </y:NodeLabel>
588 <y:Shape type="roundrectangle"/>
589 </y:ShapeNode>
590 </data>
591 </node>
592 <node id="n39">
593 <data key="d6">
594 <y:ShapeNode>
595 <y:Geometry height="37.68809849521194" width="60.59999999999985" x="130.24320000000017" y="474.4608862490102"/>
596 <y:Fill color="#FFCC00" transparent="false"/>
597 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
598 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="36.28515625" x="12.157421874999926" xml:space="preserve" y="9.859674247605994">Scale<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
599 <y:Shape type="roundrectangle"/>
600 </y:ShapeNode>
601 </data>
602 </node>
603 <node id="n40">
604 <data key="d6">
605 <y:ShapeNode>
606 <y:Geometry height="37.0" width="37.0" x="439.382496" y="697.2655495792249"/>
607 <y:Fill color="#996600" transparent="false"/>
608 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
609 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="16.5" y="16.5">
610 <y:LabelModel>
611 <y:SmartNodeLabelModel distance="4.0"/>
612 </y:LabelModel>
613 <y:ModelParameter>
614 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
615 </y:ModelParameter>
616 </y:NodeLabel>
617 <y:Shape type="ellipse"/>
618 </y:ShapeNode>
619 </data>
620 </node>
621 <node id="n41">
622 <data key="d6">
623 <y:ShapeNode>
624 <y:Geometry height="37.0" width="37.0" x="435.37600000000003" y="693.4079538714092"/>
625 <y:Fill color="#CC9900" transparent="false"/>
626 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
627 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="16.5" y="16.5">
628 <y:LabelModel>
629 <y:SmartNodeLabelModel distance="4.0"/>
630 </y:LabelModel>
631 <y:ModelParameter>
632 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
633 </y:ModelParameter>
634 </y:NodeLabel>
635 <y:Shape type="ellipse"/>
636 </y:ShapeNode>
637 </data>
638 </node>
639 <node id="n42">
640 <data key="d6">
641 <y:ShapeNode>
642 <y:Geometry height="37.0" width="37.0" x="431.5" y="689.0885511792251"/>
643 <y:Fill color="#FFCC00" transparent="false"/>
644 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
645 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="3.9697265625" xml:space="preserve" y="9.515625">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
646 <y:Shape type="ellipse"/>
647 </y:ShapeNode>
648 </data>
649 </node>
650 <node id="n43">
651 <data key="d6">
652 <y:ShapeNode>
653 <y:Geometry height="37.0" width="37.0" x="806.916416" y="585.3489404374158"/>
654 <y:Fill color="#996600" transparent="false"/>
655 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
656 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="16.5" y="16.5">
657 <y:LabelModel>
658 <y:SmartNodeLabelModel distance="4.0"/>
659 </y:LabelModel>
660 <y:ModelParameter>
661 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
662 </y:ModelParameter>
663 </y:NodeLabel>
664 <y:Shape type="ellipse"/>
665 </y:ShapeNode>
666 </data>
667 </node>
668 <node id="n44">
669 <data key="d6">
670 <y:ShapeNode>
671 <y:Geometry height="37.0" width="37.0" x="802.90992" y="581.4913447296001"/>
672 <y:Fill color="#CC9900" transparent="false"/>
673 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
674 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="16.5" y="16.5">
675 <y:LabelModel>
676 <y:SmartNodeLabelModel distance="4.0"/>
677 </y:LabelModel>
678 <y:ModelParameter>
679 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
680 </y:ModelParameter>
681 </y:NodeLabel>
682 <y:Shape type="ellipse"/>
683 </y:ShapeNode>
684 </data>
685 </node>
686 <node id="n45">
687 <data key="d6">
688 <y:ShapeNode>
689 <y:Geometry height="37.0" width="37.0" x="799.03392" y="577.171942037416"/>
690 <y:Fill color="#FFCC00" transparent="false"/>
691 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
692 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.060546875" x="3.9697265625" xml:space="preserve" y="9.515625">Mux<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
693 <y:Shape type="ellipse"/>
694 </y:ShapeNode>
695 </data>
696 </node>
697 <node id="n46">
698 <data key="d6">
699 <y:ShapeNode>
700 <y:Geometry height="50.0" width="131.0" x="875.3368590080003" y="581.8206574592001"/>
701 <y:Fill color="#0066CC" transparent="false"/>
702 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
703 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
704 <y:LabelModel>
705 <y:SmartNodeLabelModel distance="4.0"/>
706 </y:LabelModel>
707 <y:ModelParameter>
708 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
709 </y:ModelParameter>
710 </y:NodeLabel>
711 <y:Shape type="fatarrow"/>
712 </y:ShapeNode>
713 </data>
714 </node>
715 <node id="n47">
716 <data key="d6">
717 <y:ShapeNode>
718 <y:Geometry height="50.0" width="131.0" x="872.5937841920002" y="576.2884901888"/>
719 <y:Fill color="#6699FF" transparent="false"/>
720 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
721 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
722 <y:LabelModel>
723 <y:SmartNodeLabelModel distance="4.0"/>
724 </y:LabelModel>
725 <y:ModelParameter>
726 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
727 </y:ModelParameter>
728 </y:NodeLabel>
729 <y:Shape type="fatarrow"/>
730 </y:ShapeNode>
731 </data>
732 </node>
733 <node id="n48">
734 <data key="d6">
735 <y:ShapeNode>
736 <y:Geometry height="50.0" width="131.0" x="869.7499999999999" y="570.671942037416"/>
737 <y:Fill color="#99CCFF" transparent="false"/>
738 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
739 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="57.66015625" x="36.669921875" xml:space="preserve" y="2.046875">Sources
740 Live
741 :15001...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
742 <y:Shape type="fatarrow"/>
743 </y:ShapeNode>
744 </data>
745 </node>
746 <node id="n49">
747 <data key="d6">
748 <y:ShapeNode>
749 <y:Geometry height="50.0" width="131.0" x="100.80573363200017" y="682.5885511792251"/>
750 <y:Fill color="#0066CC" transparent="false"/>
751 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
752 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
753 <y:LabelModel>
754 <y:SmartNodeLabelModel distance="4.0"/>
755 </y:LabelModel>
756 <y:ModelParameter>
757 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
758 </y:ModelParameter>
759 </y:NodeLabel>
760 <y:Shape type="fatarrow"/>
761 </y:ShapeNode>
762 </data>
763 </node>
764 <node id="n50">
765 <data key="d6">
766 <y:ShapeNode>
767 <y:Geometry height="50.0" width="131.0" x="98.06265881600007" y="677.056383908825"/>
768 <y:Fill color="#6699FF" transparent="false"/>
769 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
770 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
771 <y:LabelModel>
772 <y:SmartNodeLabelModel distance="4.0"/>
773 </y:LabelModel>
774 <y:ModelParameter>
775 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
776 </y:ModelParameter>
777 </y:NodeLabel>
778 <y:Shape type="fatarrow"/>
779 </y:ShapeNode>
780 </data>
781 </node>
782 <node id="n51">
783 <data key="d6">
784 <y:ShapeNode>
785 <y:Geometry height="50.0" width="131.0" x="95.04320000000007" y="671.4169031792248"/>
786 <y:Fill color="#99CCFF" transparent="false"/>
787 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
788 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="57.66015625" x="36.669921875" xml:space="preserve" y="2.046875">Sources
789 Preview
790 :13100...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
791 <y:Shape type="fatarrow"/>
792 </y:ShapeNode>
793 </data>
794 </node>
795 <node id="n52">
796 <data key="d6">
797 <y:ShapeNode>
798 <y:Geometry height="50.0" width="131.0" x="98.16253363200019" y="334.9287680000001"/>
799 <y:Fill color="#009933" transparent="false"/>
800 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
801 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.500000000000114" y="23.0">
802 <y:LabelModel>
803 <y:SmartNodeLabelModel distance="4.0"/>
804 </y:LabelModel>
805 <y:ModelParameter>
806 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
807 </y:ModelParameter>
808 </y:NodeLabel>
809 <y:Shape type="fatarrow"/>
810 </y:ShapeNode>
811 </data>
812 </node>
813 <node id="n53">
814 <data key="d6">
815 <y:ShapeNode>
816 <y:Geometry height="50.0" width="131.0" x="95.41945881600009" y="329.3966007296"/>
817 <y:Fill color="#00CC66" transparent="false"/>
818 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
819 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
820 <y:LabelModel>
821 <y:SmartNodeLabelModel distance="4.0"/>
822 </y:LabelModel>
823 <y:ModelParameter>
824 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
825 </y:ModelParameter>
826 </y:NodeLabel>
827 <y:Shape type="fatarrow"/>
828 </y:ShapeNode>
829 </data>
830 </node>
831 <node id="n54">
832 <data key="d6">
833 <y:ShapeNode>
834 <y:Geometry height="50.0" width="131.0" x="92.40000000000009" y="323.7571199999999"/>
835 <y:Fill color="#CCFFCC" transparent="false"/>
836 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
837 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="31.9375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="57.66015625" x="36.669921875" xml:space="preserve" y="9.03125">Sources
838 :10000...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
839 <y:Shape type="fatarrow"/>
840 </y:ShapeNode>
841 </data>
842 </node>
843 <node id="n55">
844 <data key="d6">
845 <y:ShapeNode>
846 <y:Geometry height="50.0" width="131.0" x="95.1430748160002" y="185.0"/>
847 <y:Fill color="#009933" transparent="false"/>
848 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
849 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
850 <y:LabelModel>
851 <y:SmartNodeLabelModel distance="4.0"/>
852 </y:LabelModel>
853 <y:ModelParameter>
854 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
855 </y:ModelParameter>
856 </y:NodeLabel>
857 <y:Shape type="fatarrow"/>
858 </y:ShapeNode>
859 </data>
860 </node>
861 <node id="n56">
862 <data key="d6">
863 <y:ShapeNode>
864 <y:Geometry height="50.0" width="131.0" x="92.40000000000009" y="179.46783272959988"/>
865 <y:Fill color="#00CC66" transparent="false"/>
866 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
867 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
868 <y:LabelModel>
869 <y:SmartNodeLabelModel distance="4.0"/>
870 </y:LabelModel>
871 <y:ModelParameter>
872 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
873 </y:ModelParameter>
874 </y:NodeLabel>
875 <y:Shape type="fatarrow"/>
876 </y:ShapeNode>
877 </data>
878 </node>
879 <node id="n57">
880 <data key="d6">
881 <y:ShapeNode>
882 <y:Geometry height="50.0" width="131.0" x="89.3805411840001" y="173.82835199999977"/>
883 <y:Fill color="#CCFFCC" transparent="false"/>
884 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
885 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="69.666015625" x="30.6669921875" xml:space="preserve" y="2.046875">Overlay
886 Sources
887 (local files)<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
888 <y:Shape type="fatarrow"/>
889 </y:ShapeNode>
890 </data>
891 </node>
892 <node id="n58">
893 <data key="d6">
894 <y:ShapeNode>
895 <y:Geometry height="50.0" width="131.0" x="520.3119387520001" y="185.0"/>
896 <y:Fill color="#009933" transparent="false"/>
897 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
898 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
899 <y:LabelModel>
900 <y:SmartNodeLabelModel distance="4.0"/>
901 </y:LabelModel>
902 <y:ModelParameter>
903 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
904 </y:ModelParameter>
905 </y:NodeLabel>
906 <y:Shape type="fatarrow"/>
907 </y:ShapeNode>
908 </data>
909 </node>
910 <node id="n59">
911 <data key="d6">
912 <y:ShapeNode>
913 <y:Geometry height="50.0" width="131.0" x="517.568863936" y="179.46783272959988"/>
914 <y:Fill color="#00CC66" transparent="false"/>
915 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
916 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="63.5" y="23.0">
917 <y:LabelModel>
918 <y:SmartNodeLabelModel distance="4.0"/>
919 </y:LabelModel>
920 <y:ModelParameter>
921 <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
922 </y:ModelParameter>
923 </y:NodeLabel>
924 <y:Shape type="fatarrow"/>
925 </y:ShapeNode>
926 </data>
927 </node>
928 <node id="n60">
929 <data key="d6">
930 <y:ShapeNode>
931 <y:Geometry height="50.0" width="131.0" x="514.54940512" y="173.82835199999977"/>
932 <y:Fill color="#CCFFCC" transparent="false"/>
933 <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
934 <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="45.90625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="89.4296875" x="20.78515625" xml:space="preserve" y="2.0468749999999716">Blinding
935 Video Sources
936 :17000...<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
937 <y:Shape type="fatarrow"/>
938 </y:ShapeNode>
939 </data>
940 </node>
941 <edge id="e0" source="n7" target="n5">
942 <data key="d10">
943 <y:PolyLineEdge>
944 <y:Path sx="5.060000000000116" sy="-140.6059710187081" tx="0.0" ty="59.75"/>
945 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
946 <y:Arrows source="none" target="standard"/>
947 <y:BendStyle smoothed="false"/>
948 </y:PolyLineEdge>
949 </data>
950 </edge>
951 <edge id="e1" source="n7" target="n6">
952 <data key="d10">
953 <y:PolyLineEdge>
954 <y:Path sx="3.1400000000001" sy="137.065971018708" tx="0.0" ty="-0.0"/>
955 <y:LineStyle color="#FF6600" type="line" width="6.0"/>
956 <y:Arrows source="none" target="standard"/>
957 <y:BendStyle smoothed="false"/>
958 </y:PolyLineEdge>
959 </data>
960 </edge>
961 <edge id="e2" source="n54" target="n7">
962 <data key="d10">
963 <y:PolyLineEdge>
964 <y:Path sx="0.0" sy="0.0" tx="-10.299999999999898" ty="-109.8488510187081"/>
965 <y:LineStyle color="#000000" type="line" width="6.0"/>
966 <y:Arrows source="none" target="standard"/>
967 <y:BendStyle smoothed="false"/>
968 </y:PolyLineEdge>
969 </data>
970 </edge>
971 <edge id="e3" source="n5" target="n8">
972 <data key="d10">
973 <y:PolyLineEdge>
974 <y:Path sx="-1.7971254720005163" sy="26.769999999999982" tx="0.0" ty="0.0"/>
975 <y:LineStyle color="#0000FF" type="line" width="3.0"/>
976 <y:Arrows source="none" target="standard"/>
977 <y:BendStyle smoothed="false"/>
978 </y:PolyLineEdge>
979 </data>
980 </edge>
981 <edge id="e4" source="n9" target="n5">
982 <data key="d10">
983 <y:PolyLineEdge>
984 <y:Path sx="0.0" sy="0.0" tx="-18.0" ty="10.507119999999986"/>
985 <y:LineStyle color="#0000FF" type="line" width="3.0"/>
986 <y:Arrows source="none" target="standard"/>
987 <y:BendStyle smoothed="false"/>
988 </y:PolyLineEdge>
989 </data>
990 </edge>
991 <edge id="e5" source="n6" target="n10">
992 <data key="d10">
993 <y:PolyLineEdge>
994 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
995 <y:LineStyle color="#FF6600" type="line" width="3.0"/>
996 <y:Arrows source="none" target="standard"/>
997 <y:BendStyle smoothed="false"/>
998 </y:PolyLineEdge>
999 </data>
1000 </edge>
1001 <edge id="e6" source="n8" target="n11">
1002 <data key="d10">
1003 <y:PolyLineEdge>
1004 <y:Path sx="0.0" sy="1.6300000000001091" tx="0.0" ty="0.0"/>
1005 <y:LineStyle color="#0000FF" type="line" width="3.0"/>
1006 <y:Arrows source="none" target="standard"/>
1007 <y:BendStyle smoothed="false"/>
1008 </y:PolyLineEdge>
1009 </data>
1010 </edge>
1011 <edge id="e7" source="n10" target="n11">
1012 <data key="d10">
1013 <y:PolyLineEdge>
1014 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1015 <y:LineStyle color="#FF6600" type="line" width="3.0"/>
1016 <y:Arrows source="none" target="standard"/>
1017 <y:BendStyle smoothed="false"/>
1018 </y:PolyLineEdge>
1019 </data>
1020 </edge>
1021 <edge id="e8" source="n57" target="n5">
1022 <data key="d10">
1023 <y:PolyLineEdge>
1024 <y:Path sx="0.0" sy="0.0" tx="23.61556412730789" ty="-58.25"/>
1025 <y:LineStyle color="#008000" type="line" width="6.0"/>
1026 <y:Arrows source="none" target="standard"/>
1027 <y:BendStyle smoothed="false"/>
1028 </y:PolyLineEdge>
1029 </data>
1030 </edge>
1031 <edge id="e9" source="n11" target="n12">
1032 <data key="d10">
1033 <y:PolyLineEdge>
1034 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1035 <y:LineStyle color="#000000" type="line" width="3.0"/>
1036 <y:Arrows source="none" target="standard"/>
1037 <y:BendStyle smoothed="false"/>
1038 </y:PolyLineEdge>
1039 </data>
1040 </edge>
1041 <edge id="e10" source="n60" target="n8">
1042 <data key="d10">
1043 <y:PolyLineEdge>
1044 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="-0.0">
1045 <y:Point x="707.75" y="198.82835199999977"/>
1046 </y:Path>
1047 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
1048 <y:Arrows source="none" target="standard"/>
1049 <y:BendStyle smoothed="false"/>
1050 </y:PolyLineEdge>
1051 </data>
1052 </edge>
1053 <edge id="e11" source="n14" target="n13">
1054 <data key="d10">
1055 <y:PolyLineEdge>
1056 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1057 <y:LineStyle color="#000000" type="line" width="3.0"/>
1058 <y:Arrows source="none" target="standard"/>
1059 <y:BendStyle smoothed="false"/>
1060 </y:PolyLineEdge>
1061 </data>
1062 </edge>
1063 <edge id="e12" source="n34" target="n14">
1064 <data key="d10">
1065 <y:PolyLineEdge>
1066 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1067 <y:LineStyle color="#0000FF" type="line" width="3.0"/>
1068 <y:Arrows source="none" target="standard"/>
1069 <y:BendStyle smoothed="false"/>
1070 </y:PolyLineEdge>
1071 </data>
1072 </edge>
1073 <edge id="e13" source="n6" target="n14">
1074 <data key="d10">
1075 <y:PolyLineEdge>
1076 <y:Path sx="30.0" sy="-3.361277830399672" tx="0.0" ty="0.0"/>
1077 <y:LineStyle color="#FF6600" type="line" width="3.0"/>
1078 <y:Arrows source="none" target="standard"/>
1079 <y:BendStyle smoothed="false"/>
1080 </y:PolyLineEdge>
1081 </data>
1082 </edge>
1083 <edge id="e14" source="n15" target="n10">
1084 <data key="d10">
1085 <y:PolyLineEdge>
1086 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
1087 <y:Point x="707.75" y="657.6737719544125"/>
1088 </y:Path>
1089 <y:LineStyle color="#FF6600" type="line" width="3.0"/>
1090 <y:Arrows source="none" target="standard"/>
1091 <y:BendStyle smoothed="false"/>
1092 </y:PolyLineEdge>
1093 </data>
1094 </edge>
1095 <edge id="e15" source="n7" target="n39">
1096 <data key="d10">
1097 <y:PolyLineEdge>
1098 <y:Path sx="-9.54000000000002" sy="40.84891332056304" tx="23.087688098495192" ty="6.149948842654865"/>
1099 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
1100 <y:Arrows source="none" target="standard"/>
1101 <y:BendStyle smoothed="false"/>
1102 </y:PolyLineEdge>
1103 </data>
1104 </edge>
1105 <edge id="e16" source="n39" target="n23">
1106 <data key="d10">
1107 <y:PolyLineEdge>
1108 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1109 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
1110 <y:Arrows source="none" target="standard"/>
1111 <y:BendStyle smoothed="false"/>
1112 </y:PolyLineEdge>
1113 </data>
1114 </edge>
1115 <edge id="e17" source="n7" target="n42">
1116 <data key="d10">
1117 <y:PolyLineEdge>
1118 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
1119 <y:Point x="307.5" y="707.5885511792251"/>
1120 </y:Path>
1121 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
1122 <y:Arrows source="none" target="standard"/>
1123 <y:BendStyle smoothed="false"/>
1124 </y:PolyLineEdge>
1125 </data>
1126 </edge>
1127 <edge id="e18" source="n6" target="n42">
1128 <data key="d10">
1129 <y:PolyLineEdge>
1130 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1131 <y:LineStyle color="#FF6600" type="line" width="3.0"/>
1132 <y:Arrows source="none" target="standard"/>
1133 <y:BendStyle smoothed="false"/>
1134 </y:PolyLineEdge>
1135 </data>
1136 </edge>
1137 <edge id="e19" source="n42" target="n18">
1138 <data key="d10">
1139 <y:PolyLineEdge>
1140 <y:Path sx="0.0" sy="0.0" tx="-10.09223231999988" ty="0.0"/>
1141 <y:LineStyle color="#000000" type="line" width="6.0"/>
1142 <y:Arrows source="none" target="standard"/>
1143 <y:BendStyle smoothed="false"/>
1144 </y:PolyLineEdge>
1145 </data>
1146 </edge>
1147 <edge id="e20" source="n20" target="n19">
1148 <data key="d10">
1149 <y:PolyLineEdge>
1150 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1151 <y:LineStyle color="#000000" type="line" width="3.0"/>
1152 <y:Arrows source="none" target="standard"/>
1153 <y:BendStyle smoothed="false"/>
1154 </y:PolyLineEdge>
1155 </data>
1156 </edge>
1157 <edge id="e21" source="n6" target="n20">
1158 <data key="d10">
1159 <y:PolyLineEdge>
1160 <y:Path sx="-30.0" sy="-5.361277830399672" tx="0.0" ty="0.0"/>
1161 <y:LineStyle color="#FF6600" type="line" width="3.0"/>
1162 <y:Arrows source="none" target="standard"/>
1163 <y:BendStyle smoothed="false"/>
1164 </y:PolyLineEdge>
1165 </data>
1166 </edge>
1167 <edge id="e22" source="n5" target="n20">
1168 <data key="d10">
1169 <y:PolyLineEdge>
1170 <y:Path sx="-30.0" sy="66.84872216960048" tx="0.0" ty="0.0"/>
1171 <y:LineStyle color="#0000FF" type="line" width="3.0"/>
1172 <y:Arrows source="none" target="standard"/>
1173 <y:BendStyle smoothed="false"/>
1174 </y:PolyLineEdge>
1175 </data>
1176 </edge>
1177 <edge id="e23" source="n23" target="n51">
1178 <data key="d10">
1179 <y:PolyLineEdge>
1180 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1181 <y:LineStyle color="#000000" type="line" width="6.0"/>
1182 <y:Arrows source="none" target="standard"/>
1183 <y:BendStyle smoothed="false"/>
1184 </y:PolyLineEdge>
1185 </data>
1186 </edge>
1187 <edge id="e24" source="n7" target="n23">
1188 <data key="d10">
1189 <y:PolyLineEdge>
1190 <y:Path sx="-2.340000000000032" sy="134.97747428910787" tx="0.0" ty="0.0"/>
1191 <y:LineStyle color="#FF6600" type="line" width="6.0"/>
1192 <y:Arrows source="none" target="standard"/>
1193 <y:BendStyle smoothed="false"/>
1194 </y:PolyLineEdge>
1195 </data>
1196 </edge>
1197 <edge id="e25" source="n24" target="n25">
1198 <data key="d10">
1199 <y:PolyLineEdge>
1200 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1201 <y:LineStyle color="#000000" type="line" width="3.0"/>
1202 <y:Arrows source="none" target="standard"/>
1203 <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="six_pos" modelPosition="head" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="24.4609375" x="6.727121386718693" xml:space="preserve" y="-19.968723144531396">A/V<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
1204 <y:BendStyle smoothed="false"/>
1205 </y:PolyLineEdge>
1206 </data>
1207 </edge>
1208 <edge id="e26" source="n26" target="n27">
1209 <data key="d10">
1210 <y:PolyLineEdge>
1211 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1212 <y:LineStyle color="#0000FF" type="line" width="3.0"/>
1213 <y:Arrows source="none" target="standard"/>
1214 <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="six_pos" modelPosition="head" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.884765625" x="0.01522402343749718" xml:space="preserve" y="-19.968723144531396">Video<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
1215 <y:BendStyle smoothed="false"/>
1216 </y:PolyLineEdge>
1217 </data>
1218 </edge>
1219 <edge id="e27" source="n28" target="n29">
1220 <data key="d10">
1221 <y:PolyLineEdge>
1222 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1223 <y:LineStyle color="#FF6600" type="line" width="3.0"/>
1224 <y:Arrows source="none" target="standard"/>
1225 <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="six_pos" modelPosition="head" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="38.107421875" x="-0.09610151367184017" xml:space="preserve" y="-19.968723144531396">Audio<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
1226 <y:BendStyle smoothed="false"/>
1227 </y:PolyLineEdge>
1228 </data>
1229 </edge>
1230 <edge id="e28" source="n30" target="n31">
1231 <data key="d10">
1232 <y:PolyLineEdge>
1233 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1234 <y:LineStyle color="#008000" type="line" width="3.0"/>
1235 <y:Arrows source="none" target="standard"/>
1236 <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.96875" horizontalTextPosition="center" iconTextGap="4" modelName="six_pos" modelPosition="head" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="41.58203125" x="-1.833417138671905" xml:space="preserve" y="-19.968723144531396">Image<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
1237 <y:BendStyle smoothed="false"/>
1238 </y:PolyLineEdge>
1239 </data>
1240 </edge>
1241 <edge id="e29" source="n5" target="n34">
1242 <data key="d10">
1243 <y:PolyLineEdge>
1244 <y:Path sx="30.0" sy="52.83800000000008" tx="0.0" ty="0.0"/>
1245 <y:LineStyle color="#0000FF" type="line" width="3.0"/>
1246 <y:Arrows source="none" target="standard"/>
1247 <y:BendStyle smoothed="false"/>
1248 </y:PolyLineEdge>
1249 </data>
1250 </edge>
1251 <edge id="e30" source="n35" target="n45">
1252 <data key="d10">
1253 <y:PolyLineEdge>
1254 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1255 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
1256 <y:Arrows source="none" target="standard"/>
1257 <y:BendStyle smoothed="false"/>
1258 </y:PolyLineEdge>
1259 </data>
1260 </edge>
1261 <edge id="e31" source="n60" target="n35">
1262 <data key="d10">
1263 <y:PolyLineEdge>
1264 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
1265 <y:Point x="817.53392" y="198.4999999999999"/>
1266 </y:Path>
1267 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
1268 <y:Arrows source="none" target="standard"/>
1269 <y:BendStyle smoothed="false"/>
1270 </y:PolyLineEdge>
1271 </data>
1272 </edge>
1273 <edge id="e32" source="n10" target="n45">
1274 <data key="d10">
1275 <y:PolyLineEdge>
1276 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1277 <y:LineStyle color="#FF6600" type="line" width="3.0"/>
1278 <y:Arrows source="none" target="standard"/>
1279 <y:BendStyle smoothed="false"/>
1280 </y:PolyLineEdge>
1281 </data>
1282 </edge>
1283 <edge id="e33" source="n45" target="n48">
1284 <data key="d10">
1285 <y:PolyLineEdge>
1286 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1287 <y:LineStyle color="#000000" type="line" width="6.0"/>
1288 <y:Arrows source="none" target="standard"/>
1289 <y:BendStyle smoothed="false"/>
1290 </y:PolyLineEdge>
1291 </data>
1292 </edge>
1293 <edge id="e34" source="n7" target="n36">
1294 <data key="d10">
1295 <y:PolyLineEdge>
1296 <y:Path sx="10.300000000000011" sy="-99.23497652349482" tx="0.0" ty="0.0"/>
1297 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
1298 <y:Arrows source="none" target="standard"/>
1299 <y:BendStyle smoothed="false"/>
1300 </y:PolyLineEdge>
1301 </data>
1302 </edge>
1303 <edge id="e35" source="n36" target="n35">
1304 <data key="d10">
1305 <y:PolyLineEdge>
1306 <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
1307 <y:LineStyle color="#0000FF" type="line" width="6.0"/>
1308 <y:Arrows source="none" target="standard"/>
1309 <y:BendStyle smoothed="false"/>
1310 </y:PolyLineEdge>
1311 </data>
1312 </edge>
1313 </graph>
1314 <data key="d7">
1315 <y:Resources/>
1316 </data>
1317 </graphml>
0 <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill-opacity="1" color-rendering="auto" color-interpolation="auto" text-rendering="auto" stroke="black" stroke-linecap="square" width="948" stroke-miterlimit="10" shape-rendering="auto" stroke-opacity="1" fill="black" stroke-dasharray="none" font-weight="normal" stroke-width="1" height="661" font-family="'Dialog'" font-style="normal" stroke-linejoin="miter" font-size="12px" stroke-dashoffset="0" image-rendering="auto">
1 <!--Generated by ySVG 2.5-->
2 <defs id="genericDefs"/>
3 <g>
4 <defs id="defs1">
5 <clipPath clipPathUnits="userSpaceOnUse" id="clipPath1">
6 <path d="M0 0 L948 0 L948 661 L0 661 L0 0 Z"/>
7 </clipPath>
8 <clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
9 <path d="M74 158 L1022 158 L1022 819 L74 819 L74 158 Z"/>
10 </clipPath>
11 <clipPath clipPathUnits="userSpaceOnUse" id="clipPath3">
12 <path d="M323.4478 -224.5156 L323.4478 723.4844 L-337.5522 723.4844 L-337.5522 -224.5156 L323.4478 -224.5156 Z"/>
13 </clipPath>
14 </defs>
15 <g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="translate(-74,-158)" stroke="white">
16 <rect x="74" width="948" height="661" y="158" clip-path="url(#clipPath2)" stroke="none"/>
17 </g>
18 <g fill="rgb(220,220,220)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="rgb(220,220,220)">
19 <line y2="-37" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="-39"/>
20 <line y2="-38" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="-38"/>
21 <line y2="-7" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="-9"/>
22 <line y2="-8" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="-8"/>
23 <line y2="23" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="21"/>
24 <line y2="22" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="22"/>
25 <line y2="53" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="51"/>
26 <line y2="52" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="52"/>
27 <line y2="83" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="81"/>
28 <line y2="82" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="82"/>
29 <line y2="113" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="111"/>
30 <line y2="112" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="112"/>
31 <line y2="143" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="141"/>
32 <line y2="142" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="142"/>
33 <line y2="173" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="171"/>
34 <line y2="172" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="172"/>
35 <line y2="203" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="201"/>
36 <line y2="202" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="202"/>
37 <line y2="233" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="231"/>
38 <line y2="232" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="232"/>
39 <line y2="263" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="261"/>
40 <line y2="262" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="262"/>
41 <line y2="293" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="291"/>
42 <line y2="292" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="292"/>
43 <line y2="323" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="321"/>
44 <line y2="322" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="322"/>
45 <line y2="353" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="351"/>
46 <line y2="352" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="352"/>
47 <line y2="383" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="381"/>
48 <line y2="382" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="382"/>
49 <line y2="413" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="411"/>
50 <line y2="412" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="412"/>
51 <line y2="443" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="441"/>
52 <line y2="442" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="442"/>
53 <line y2="473" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="471"/>
54 <line y2="472" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="472"/>
55 <line y2="503" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="501"/>
56 <line y2="502" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="502"/>
57 <line y2="533" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="531"/>
58 <line y2="532" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="532"/>
59 <line y2="563" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="561"/>
60 <line y2="562" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="562"/>
61 <line y2="593" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="591"/>
62 <line y2="592" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="592"/>
63 <line y2="623" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="621"/>
64 <line y2="622" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="622"/>
65 <line y2="653" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="651"/>
66 <line y2="652" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="652"/>
67 <line y2="683" fill="none" x1="-44" clip-path="url(#clipPath1)" x2="-44" y1="681"/>
68 <line y2="682" fill="none" x1="-45" clip-path="url(#clipPath1)" x2="-43" y1="682"/>
69 <line y2="-37" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="-39"/>
70 <line y2="-38" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="-38"/>
71 <line y2="-7" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="-9"/>
72 <line y2="-8" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="-8"/>
73 <line y2="23" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="21"/>
74 <line y2="22" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="22"/>
75 <line y2="53" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="51"/>
76 <line y2="52" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="52"/>
77 <line y2="83" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="81"/>
78 <line y2="82" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="82"/>
79 <line y2="113" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="111"/>
80 <line y2="112" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="112"/>
81 <line y2="143" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="141"/>
82 <line y2="142" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="142"/>
83 <line y2="173" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="171"/>
84 <line y2="172" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="172"/>
85 <line y2="203" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="201"/>
86 <line y2="202" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="202"/>
87 <line y2="233" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="231"/>
88 <line y2="232" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="232"/>
89 <line y2="263" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="261"/>
90 <line y2="262" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="262"/>
91 <line y2="293" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="291"/>
92 <line y2="292" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="292"/>
93 <line y2="323" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="321"/>
94 <line y2="322" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="322"/>
95 <line y2="353" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="351"/>
96 <line y2="352" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="352"/>
97 <line y2="383" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="381"/>
98 <line y2="382" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="382"/>
99 <line y2="413" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="411"/>
100 <line y2="412" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="412"/>
101 <line y2="443" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="441"/>
102 <line y2="442" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="442"/>
103 <line y2="473" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="471"/>
104 <line y2="472" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="472"/>
105 <line y2="503" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="501"/>
106 <line y2="502" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="502"/>
107 <line y2="533" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="531"/>
108 <line y2="532" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="532"/>
109 <line y2="563" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="561"/>
110 <line y2="562" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="562"/>
111 <line y2="593" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="591"/>
112 <line y2="592" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="592"/>
113 <line y2="623" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="621"/>
114 <line y2="622" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="622"/>
115 <line y2="653" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="651"/>
116 <line y2="652" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="652"/>
117 <line y2="683" fill="none" x1="-14" clip-path="url(#clipPath1)" x2="-14" y1="681"/>
118 <line y2="682" fill="none" x1="-15" clip-path="url(#clipPath1)" x2="-13" y1="682"/>
119 <line y2="-37" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="-39"/>
120 <line y2="-38" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="-38"/>
121 <line y2="-7" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="-9"/>
122 <line y2="-8" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="-8"/>
123 <line y2="23" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="21"/>
124 <line y2="22" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="22"/>
125 <line y2="53" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="51"/>
126 <line y2="52" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="52"/>
127 <line y2="83" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="81"/>
128 <line y2="82" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="82"/>
129 <line y2="113" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="111"/>
130 <line y2="112" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="112"/>
131 <line y2="143" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="141"/>
132 <line y2="142" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="142"/>
133 <line y2="173" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="171"/>
134 <line y2="172" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="172"/>
135 <line y2="203" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="201"/>
136 <line y2="202" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="202"/>
137 <line y2="233" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="231"/>
138 <line y2="232" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="232"/>
139 <line y2="263" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="261"/>
140 <line y2="262" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="262"/>
141 <line y2="293" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="291"/>
142 <line y2="292" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="292"/>
143 <line y2="323" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="321"/>
144 <line y2="322" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="322"/>
145 <line y2="353" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="351"/>
146 <line y2="352" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="352"/>
147 <line y2="383" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="381"/>
148 <line y2="382" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="382"/>
149 <line y2="413" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="411"/>
150 <line y2="412" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="412"/>
151 <line y2="443" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="441"/>
152 <line y2="442" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="442"/>
153 <line y2="473" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="471"/>
154 <line y2="472" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="472"/>
155 <line y2="503" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="501"/>
156 <line y2="502" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="502"/>
157 <line y2="533" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="531"/>
158 <line y2="532" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="532"/>
159 <line y2="563" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="561"/>
160 <line y2="562" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="562"/>
161 <line y2="593" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="591"/>
162 <line y2="592" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="592"/>
163 <line y2="623" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="621"/>
164 <line y2="622" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="622"/>
165 <line y2="653" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="651"/>
166 <line y2="652" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="652"/>
167 <line y2="683" fill="none" x1="16" clip-path="url(#clipPath1)" x2="16" y1="681"/>
168 <line y2="682" fill="none" x1="15" clip-path="url(#clipPath1)" x2="17" y1="682"/>
169 <line y2="-37" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="-39"/>
170 <line y2="-38" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="-38"/>
171 <line y2="-7" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="-9"/>
172 <line y2="-8" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="-8"/>
173 <line y2="23" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="21"/>
174 <line y2="22" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="22"/>
175 <line y2="53" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="51"/>
176 <line y2="52" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="52"/>
177 <line y2="83" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="81"/>
178 <line y2="82" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="82"/>
179 <line y2="113" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="111"/>
180 <line y2="112" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="112"/>
181 <line y2="143" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="141"/>
182 <line y2="142" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="142"/>
183 <line y2="173" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="171"/>
184 <line y2="172" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="172"/>
185 <line y2="203" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="201"/>
186 <line y2="202" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="202"/>
187 <line y2="233" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="231"/>
188 <line y2="232" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="232"/>
189 <line y2="263" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="261"/>
190 <line y2="262" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="262"/>
191 <line y2="293" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="291"/>
192 <line y2="292" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="292"/>
193 <line y2="323" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="321"/>
194 <line y2="322" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="322"/>
195 <line y2="353" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="351"/>
196 <line y2="352" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="352"/>
197 <line y2="383" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="381"/>
198 <line y2="382" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="382"/>
199 <line y2="413" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="411"/>
200 <line y2="412" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="412"/>
201 <line y2="443" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="441"/>
202 <line y2="442" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="442"/>
203 <line y2="473" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="471"/>
204 <line y2="472" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="472"/>
205 <line y2="503" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="501"/>
206 <line y2="502" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="502"/>
207 <line y2="533" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="531"/>
208 <line y2="532" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="532"/>
209 <line y2="563" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="561"/>
210 <line y2="562" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="562"/>
211 <line y2="593" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="591"/>
212 <line y2="592" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="592"/>
213 <line y2="623" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="621"/>
214 <line y2="622" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="622"/>
215 <line y2="653" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="651"/>
216 <line y2="652" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="652"/>
217 <line y2="683" fill="none" x1="46" clip-path="url(#clipPath1)" x2="46" y1="681"/>
218 <line y2="682" fill="none" x1="45" clip-path="url(#clipPath1)" x2="47" y1="682"/>
219 <line y2="-37" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="-39"/>
220 <line y2="-38" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="-38"/>
221 <line y2="-7" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="-9"/>
222 <line y2="-8" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="-8"/>
223 <line y2="23" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="21"/>
224 <line y2="22" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="22"/>
225 <line y2="53" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="51"/>
226 <line y2="52" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="52"/>
227 <line y2="83" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="81"/>
228 <line y2="82" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="82"/>
229 <line y2="113" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="111"/>
230 <line y2="112" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="112"/>
231 <line y2="143" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="141"/>
232 <line y2="142" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="142"/>
233 <line y2="173" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="171"/>
234 <line y2="172" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="172"/>
235 <line y2="203" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="201"/>
236 <line y2="202" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="202"/>
237 <line y2="233" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="231"/>
238 <line y2="232" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="232"/>
239 <line y2="263" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="261"/>
240 <line y2="262" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="262"/>
241 <line y2="293" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="291"/>
242 <line y2="292" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="292"/>
243 <line y2="323" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="321"/>
244 <line y2="322" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="322"/>
245 <line y2="353" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="351"/>
246 <line y2="352" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="352"/>
247 <line y2="383" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="381"/>
248 <line y2="382" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="382"/>
249 <line y2="413" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="411"/>
250 <line y2="412" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="412"/>
251 <line y2="443" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="441"/>
252 <line y2="442" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="442"/>
253 <line y2="473" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="471"/>
254 <line y2="472" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="472"/>
255 <line y2="503" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="501"/>
256 <line y2="502" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="502"/>
257 <line y2="533" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="531"/>
258 <line y2="532" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="532"/>
259 <line y2="563" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="561"/>
260 <line y2="562" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="562"/>
261 <line y2="593" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="591"/>
262 <line y2="592" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="592"/>
263 <line y2="623" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="621"/>
264 <line y2="622" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="622"/>
265 <line y2="653" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="651"/>
266 <line y2="652" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="652"/>
267 <line y2="683" fill="none" x1="76" clip-path="url(#clipPath1)" x2="76" y1="681"/>
268 <line y2="682" fill="none" x1="75" clip-path="url(#clipPath1)" x2="77" y1="682"/>
269 <line y2="-37" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="-39"/>
270 <line y2="-38" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="-38"/>
271 <line y2="-7" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="-9"/>
272 <line y2="-8" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="-8"/>
273 <line y2="23" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="21"/>
274 <line y2="22" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="22"/>
275 <line y2="53" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="51"/>
276 <line y2="52" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="52"/>
277 <line y2="83" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="81"/>
278 <line y2="82" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="82"/>
279 <line y2="113" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="111"/>
280 <line y2="112" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="112"/>
281 <line y2="143" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="141"/>
282 <line y2="142" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="142"/>
283 <line y2="173" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="171"/>
284 <line y2="172" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="172"/>
285 <line y2="203" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="201"/>
286 <line y2="202" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="202"/>
287 <line y2="233" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="231"/>
288 <line y2="232" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="232"/>
289 <line y2="263" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="261"/>
290 <line y2="262" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="262"/>
291 <line y2="293" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="291"/>
292 <line y2="292" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="292"/>
293 <line y2="323" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="321"/>
294 <line y2="322" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="322"/>
295 <line y2="353" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="351"/>
296 <line y2="352" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="352"/>
297 <line y2="383" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="381"/>
298 <line y2="382" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="382"/>
299 <line y2="413" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="411"/>
300 <line y2="412" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="412"/>
301 <line y2="443" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="441"/>
302 <line y2="442" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="442"/>
303 <line y2="473" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="471"/>
304 <line y2="472" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="472"/>
305 <line y2="503" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="501"/>
306 <line y2="502" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="502"/>
307 <line y2="533" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="531"/>
308 <line y2="532" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="532"/>
309 <line y2="563" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="561"/>
310 <line y2="562" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="562"/>
311 <line y2="593" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="591"/>
312 <line y2="592" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="592"/>
313 <line y2="623" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="621"/>
314 <line y2="622" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="622"/>
315 <line y2="653" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="651"/>
316 <line y2="652" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="652"/>
317 <line y2="683" fill="none" x1="106" clip-path="url(#clipPath1)" x2="106" y1="681"/>
318 <line y2="682" fill="none" x1="105" clip-path="url(#clipPath1)" x2="107" y1="682"/>
319 <line y2="-37" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="-39"/>
320 <line y2="-38" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="-38"/>
321 <line y2="-7" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="-9"/>
322 <line y2="-8" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="-8"/>
323 <line y2="23" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="21"/>
324 <line y2="22" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="22"/>
325 <line y2="53" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="51"/>
326 <line y2="52" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="52"/>
327 <line y2="83" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="81"/>
328 <line y2="82" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="82"/>
329 <line y2="113" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="111"/>
330 <line y2="112" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="112"/>
331 <line y2="143" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="141"/>
332 <line y2="142" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="142"/>
333 <line y2="173" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="171"/>
334 <line y2="172" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="172"/>
335 <line y2="203" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="201"/>
336 <line y2="202" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="202"/>
337 <line y2="233" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="231"/>
338 <line y2="232" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="232"/>
339 <line y2="263" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="261"/>
340 <line y2="262" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="262"/>
341 <line y2="293" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="291"/>
342 <line y2="292" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="292"/>
343 <line y2="323" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="321"/>
344 <line y2="322" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="322"/>
345 <line y2="353" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="351"/>
346 <line y2="352" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="352"/>
347 <line y2="383" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="381"/>
348 <line y2="382" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="382"/>
349 <line y2="413" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="411"/>
350 <line y2="412" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="412"/>
351 <line y2="443" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="441"/>
352 <line y2="442" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="442"/>
353 <line y2="473" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="471"/>
354 <line y2="472" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="472"/>
355 <line y2="503" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="501"/>
356 <line y2="502" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="502"/>
357 <line y2="533" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="531"/>
358 <line y2="532" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="532"/>
359 <line y2="563" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="561"/>
360 <line y2="562" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="562"/>
361 <line y2="593" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="591"/>
362 <line y2="592" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="592"/>
363 <line y2="623" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="621"/>
364 <line y2="622" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="622"/>
365 <line y2="653" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="651"/>
366 <line y2="652" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="652"/>
367 <line y2="683" fill="none" x1="136" clip-path="url(#clipPath1)" x2="136" y1="681"/>
368 <line y2="682" fill="none" x1="135" clip-path="url(#clipPath1)" x2="137" y1="682"/>
369 <line y2="-37" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="-39"/>
370 <line y2="-38" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="-38"/>
371 <line y2="-7" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="-9"/>
372 <line y2="-8" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="-8"/>
373 <line y2="23" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="21"/>
374 <line y2="22" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="22"/>
375 <line y2="53" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="51"/>
376 <line y2="52" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="52"/>
377 <line y2="83" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="81"/>
378 <line y2="82" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="82"/>
379 <line y2="113" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="111"/>
380 <line y2="112" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="112"/>
381 <line y2="143" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="141"/>
382 <line y2="142" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="142"/>
383 <line y2="173" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="171"/>
384 <line y2="172" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="172"/>
385 <line y2="203" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="201"/>
386 <line y2="202" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="202"/>
387 <line y2="233" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="231"/>
388 <line y2="232" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="232"/>
389 <line y2="263" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="261"/>
390 <line y2="262" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="262"/>
391 <line y2="293" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="291"/>
392 <line y2="292" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="292"/>
393 <line y2="323" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="321"/>
394 <line y2="322" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="322"/>
395 <line y2="353" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="351"/>
396 <line y2="352" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="352"/>
397 <line y2="383" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="381"/>
398 <line y2="382" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="382"/>
399 <line y2="413" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="411"/>
400 <line y2="412" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="412"/>
401 <line y2="443" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="441"/>
402 <line y2="442" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="442"/>
403 <line y2="473" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="471"/>
404 <line y2="472" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="472"/>
405 <line y2="503" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="501"/>
406 <line y2="502" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="502"/>
407 <line y2="533" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="531"/>
408 <line y2="532" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="532"/>
409 <line y2="563" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="561"/>
410 <line y2="562" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="562"/>
411 <line y2="593" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="591"/>
412 <line y2="592" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="592"/>
413 <line y2="623" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="621"/>
414 <line y2="622" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="622"/>
415 <line y2="653" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="651"/>
416 <line y2="652" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="652"/>
417 <line y2="683" fill="none" x1="166" clip-path="url(#clipPath1)" x2="166" y1="681"/>
418 <line y2="682" fill="none" x1="165" clip-path="url(#clipPath1)" x2="167" y1="682"/>
419 <line y2="-37" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="-39"/>
420 <line y2="-38" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="-38"/>
421 <line y2="-7" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="-9"/>
422 <line y2="-8" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="-8"/>
423 <line y2="23" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="21"/>
424 <line y2="22" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="22"/>
425 <line y2="53" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="51"/>
426 <line y2="52" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="52"/>
427 <line y2="83" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="81"/>
428 <line y2="82" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="82"/>
429 <line y2="113" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="111"/>
430 <line y2="112" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="112"/>
431 <line y2="143" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="141"/>
432 <line y2="142" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="142"/>
433 <line y2="173" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="171"/>
434 <line y2="172" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="172"/>
435 <line y2="203" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="201"/>
436 <line y2="202" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="202"/>
437 <line y2="233" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="231"/>
438 <line y2="232" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="232"/>
439 <line y2="263" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="261"/>
440 <line y2="262" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="262"/>
441 <line y2="293" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="291"/>
442 <line y2="292" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="292"/>
443 <line y2="323" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="321"/>
444 <line y2="322" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="322"/>
445 <line y2="353" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="351"/>
446 <line y2="352" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="352"/>
447 <line y2="383" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="381"/>
448 <line y2="382" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="382"/>
449 <line y2="413" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="411"/>
450 <line y2="412" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="412"/>
451 <line y2="443" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="441"/>
452 <line y2="442" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="442"/>
453 <line y2="473" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="471"/>
454 <line y2="472" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="472"/>
455 <line y2="503" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="501"/>
456 <line y2="502" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="502"/>
457 <line y2="533" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="531"/>
458 <line y2="532" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="532"/>
459 <line y2="563" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="561"/>
460 <line y2="562" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="562"/>
461 <line y2="593" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="591"/>
462 <line y2="592" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="592"/>
463 <line y2="623" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="621"/>
464 <line y2="622" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="622"/>
465 <line y2="653" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="651"/>
466 <line y2="652" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="652"/>
467 <line y2="683" fill="none" x1="196" clip-path="url(#clipPath1)" x2="196" y1="681"/>
468 <line y2="682" fill="none" x1="195" clip-path="url(#clipPath1)" x2="197" y1="682"/>
469 <line y2="-37" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="-39"/>
470 <line y2="-38" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="-38"/>
471 <line y2="-7" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="-9"/>
472 <line y2="-8" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="-8"/>
473 <line y2="23" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="21"/>
474 <line y2="22" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="22"/>
475 <line y2="53" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="51"/>
476 <line y2="52" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="52"/>
477 <line y2="83" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="81"/>
478 <line y2="82" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="82"/>
479 <line y2="113" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="111"/>
480 <line y2="112" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="112"/>
481 <line y2="143" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="141"/>
482 <line y2="142" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="142"/>
483 <line y2="173" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="171"/>
484 <line y2="172" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="172"/>
485 <line y2="203" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="201"/>
486 <line y2="202" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="202"/>
487 <line y2="233" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="231"/>
488 <line y2="232" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="232"/>
489 <line y2="263" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="261"/>
490 <line y2="262" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="262"/>
491 <line y2="293" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="291"/>
492 <line y2="292" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="292"/>
493 <line y2="323" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="321"/>
494 <line y2="322" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="322"/>
495 <line y2="353" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="351"/>
496 <line y2="352" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="352"/>
497 <line y2="383" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="381"/>
498 <line y2="382" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="382"/>
499 <line y2="413" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="411"/>
500 <line y2="412" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="412"/>
501 <line y2="443" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="441"/>
502 <line y2="442" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="442"/>
503 <line y2="473" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="471"/>
504 <line y2="472" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="472"/>
505 <line y2="503" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="501"/>
506 <line y2="502" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="502"/>
507 <line y2="533" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="531"/>
508 <line y2="532" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="532"/>
509 <line y2="563" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="561"/>
510 <line y2="562" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="562"/>
511 <line y2="593" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="591"/>
512 <line y2="592" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="592"/>
513 <line y2="623" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="621"/>
514 <line y2="622" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="622"/>
515 <line y2="653" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="651"/>
516 <line y2="652" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="652"/>
517 <line y2="683" fill="none" x1="226" clip-path="url(#clipPath1)" x2="226" y1="681"/>
518 <line y2="682" fill="none" x1="225" clip-path="url(#clipPath1)" x2="227" y1="682"/>
519 <line y2="-37" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="-39"/>
520 <line y2="-38" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="-38"/>
521 <line y2="-7" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="-9"/>
522 <line y2="-8" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="-8"/>
523 <line y2="23" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="21"/>
524 <line y2="22" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="22"/>
525 <line y2="53" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="51"/>
526 <line y2="52" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="52"/>
527 <line y2="83" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="81"/>
528 <line y2="82" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="82"/>
529 <line y2="113" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="111"/>
530 <line y2="112" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="112"/>
531 <line y2="143" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="141"/>
532 <line y2="142" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="142"/>
533 <line y2="173" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="171"/>
534 <line y2="172" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="172"/>
535 <line y2="203" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="201"/>
536 <line y2="202" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="202"/>
537 <line y2="233" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="231"/>
538 <line y2="232" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="232"/>
539 <line y2="263" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="261"/>
540 <line y2="262" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="262"/>
541 <line y2="293" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="291"/>
542 <line y2="292" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="292"/>
543 <line y2="323" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="321"/>
544 <line y2="322" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="322"/>
545 <line y2="353" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="351"/>
546 <line y2="352" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="352"/>
547 <line y2="383" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="381"/>
548 <line y2="382" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="382"/>
549 <line y2="413" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="411"/>
550 <line y2="412" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="412"/>
551 <line y2="443" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="441"/>
552 <line y2="442" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="442"/>
553 <line y2="473" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="471"/>
554 <line y2="472" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="472"/>
555 <line y2="503" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="501"/>
556 <line y2="502" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="502"/>
557 <line y2="533" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="531"/>
558 <line y2="532" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="532"/>
559 <line y2="563" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="561"/>
560 <line y2="562" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="562"/>
561 <line y2="593" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="591"/>
562 <line y2="592" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="592"/>
563 <line y2="623" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="621"/>
564 <line y2="622" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="622"/>
565 <line y2="653" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="651"/>
566 <line y2="652" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="652"/>
567 <line y2="683" fill="none" x1="256" clip-path="url(#clipPath1)" x2="256" y1="681"/>
568 <line y2="682" fill="none" x1="255" clip-path="url(#clipPath1)" x2="257" y1="682"/>
569 <line y2="-37" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="-39"/>
570 <line y2="-38" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="-38"/>
571 <line y2="-7" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="-9"/>
572 <line y2="-8" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="-8"/>
573 <line y2="23" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="21"/>
574 <line y2="22" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="22"/>
575 <line y2="53" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="51"/>
576 <line y2="52" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="52"/>
577 <line y2="83" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="81"/>
578 <line y2="82" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="82"/>
579 <line y2="113" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="111"/>
580 <line y2="112" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="112"/>
581 <line y2="143" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="141"/>
582 <line y2="142" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="142"/>
583 <line y2="173" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="171"/>
584 <line y2="172" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="172"/>
585 <line y2="203" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="201"/>
586 <line y2="202" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="202"/>
587 <line y2="233" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="231"/>
588 <line y2="232" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="232"/>
589 <line y2="263" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="261"/>
590 <line y2="262" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="262"/>
591 <line y2="293" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="291"/>
592 <line y2="292" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="292"/>
593 <line y2="323" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="321"/>
594 <line y2="322" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="322"/>
595 <line y2="353" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="351"/>
596 <line y2="352" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="352"/>
597 <line y2="383" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="381"/>
598 <line y2="382" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="382"/>
599 <line y2="413" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="411"/>
600 <line y2="412" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="412"/>
601 <line y2="443" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="441"/>
602 <line y2="442" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="442"/>
603 <line y2="473" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="471"/>
604 <line y2="472" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="472"/>
605 <line y2="503" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="501"/>
606 <line y2="502" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="502"/>
607 <line y2="533" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="531"/>
608 <line y2="532" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="532"/>
609 <line y2="563" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="561"/>
610 <line y2="562" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="562"/>
611 <line y2="593" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="591"/>
612 <line y2="592" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="592"/>
613 <line y2="623" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="621"/>
614 <line y2="622" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="622"/>
615 <line y2="653" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="651"/>
616 <line y2="652" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="652"/>
617 <line y2="683" fill="none" x1="286" clip-path="url(#clipPath1)" x2="286" y1="681"/>
618 <line y2="682" fill="none" x1="285" clip-path="url(#clipPath1)" x2="287" y1="682"/>
619 <line y2="-37" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="-39"/>
620 <line y2="-38" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="-38"/>
621 <line y2="-7" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="-9"/>
622 <line y2="-8" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="-8"/>
623 <line y2="23" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="21"/>
624 <line y2="22" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="22"/>
625 <line y2="53" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="51"/>
626 <line y2="52" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="52"/>
627 <line y2="83" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="81"/>
628 <line y2="82" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="82"/>
629 <line y2="113" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="111"/>
630 <line y2="112" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="112"/>
631 <line y2="143" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="141"/>
632 <line y2="142" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="142"/>
633 <line y2="173" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="171"/>
634 <line y2="172" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="172"/>
635 <line y2="203" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="201"/>
636 <line y2="202" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="202"/>
637 <line y2="233" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="231"/>
638 <line y2="232" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="232"/>
639 <line y2="263" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="261"/>
640 <line y2="262" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="262"/>
641 <line y2="293" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="291"/>
642 <line y2="292" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="292"/>
643 <line y2="323" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="321"/>
644 <line y2="322" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="322"/>
645 <line y2="353" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="351"/>
646 <line y2="352" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="352"/>
647 <line y2="383" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="381"/>
648 <line y2="382" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="382"/>
649 <line y2="413" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="411"/>
650 <line y2="412" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="412"/>
651 <line y2="443" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="441"/>
652 <line y2="442" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="442"/>
653 <line y2="473" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="471"/>
654 <line y2="472" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="472"/>
655 <line y2="503" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="501"/>
656 <line y2="502" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="502"/>
657 <line y2="533" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="531"/>
658 <line y2="532" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="532"/>
659 <line y2="563" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="561"/>
660 <line y2="562" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="562"/>
661 <line y2="593" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="591"/>
662 <line y2="592" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="592"/>
663 <line y2="623" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="621"/>
664 <line y2="622" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="622"/>
665 <line y2="653" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="651"/>
666 <line y2="652" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="652"/>
667 <line y2="683" fill="none" x1="316" clip-path="url(#clipPath1)" x2="316" y1="681"/>
668 <line y2="682" fill="none" x1="315" clip-path="url(#clipPath1)" x2="317" y1="682"/>
669 <line y2="-37" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="-39"/>
670 <line y2="-38" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="-38"/>
671 <line y2="-7" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="-9"/>
672 <line y2="-8" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="-8"/>
673 <line y2="23" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="21"/>
674 <line y2="22" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="22"/>
675 <line y2="53" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="51"/>
676 <line y2="52" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="52"/>
677 <line y2="83" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="81"/>
678 <line y2="82" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="82"/>
679 <line y2="113" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="111"/>
680 <line y2="112" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="112"/>
681 <line y2="143" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="141"/>
682 <line y2="142" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="142"/>
683 <line y2="173" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="171"/>
684 <line y2="172" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="172"/>
685 <line y2="203" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="201"/>
686 <line y2="202" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="202"/>
687 <line y2="233" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="231"/>
688 <line y2="232" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="232"/>
689 <line y2="263" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="261"/>
690 <line y2="262" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="262"/>
691 <line y2="293" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="291"/>
692 <line y2="292" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="292"/>
693 <line y2="323" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="321"/>
694 <line y2="322" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="322"/>
695 <line y2="353" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="351"/>
696 <line y2="352" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="352"/>
697 <line y2="383" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="381"/>
698 <line y2="382" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="382"/>
699 <line y2="413" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="411"/>
700 <line y2="412" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="412"/>
701 <line y2="443" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="441"/>
702 <line y2="442" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="442"/>
703 <line y2="473" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="471"/>
704 <line y2="472" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="472"/>
705 <line y2="503" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="501"/>
706 <line y2="502" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="502"/>
707 <line y2="533" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="531"/>
708 <line y2="532" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="532"/>
709 <line y2="563" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="561"/>
710 <line y2="562" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="562"/>
711 <line y2="593" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="591"/>
712 <line y2="592" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="592"/>
713 <line y2="623" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="621"/>
714 <line y2="622" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="622"/>
715 <line y2="653" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="651"/>
716 <line y2="652" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="652"/>
717 <line y2="683" fill="none" x1="346" clip-path="url(#clipPath1)" x2="346" y1="681"/>
718 <line y2="682" fill="none" x1="345" clip-path="url(#clipPath1)" x2="347" y1="682"/>
719 <line y2="-37" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="-39"/>
720 <line y2="-38" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="-38"/>
721 <line y2="-7" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="-9"/>
722 <line y2="-8" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="-8"/>
723 <line y2="23" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="21"/>
724 <line y2="22" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="22"/>
725 <line y2="53" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="51"/>
726 <line y2="52" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="52"/>
727 <line y2="83" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="81"/>
728 <line y2="82" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="82"/>
729 <line y2="113" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="111"/>
730 <line y2="112" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="112"/>
731 <line y2="143" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="141"/>
732 <line y2="142" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="142"/>
733 <line y2="173" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="171"/>
734 <line y2="172" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="172"/>
735 <line y2="203" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="201"/>
736 <line y2="202" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="202"/>
737 <line y2="233" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="231"/>
738 <line y2="232" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="232"/>
739 <line y2="263" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="261"/>
740 <line y2="262" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="262"/>
741 <line y2="293" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="291"/>
742 <line y2="292" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="292"/>
743 <line y2="323" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="321"/>
744 <line y2="322" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="322"/>
745 <line y2="353" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="351"/>
746 <line y2="352" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="352"/>
747 <line y2="383" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="381"/>
748 <line y2="382" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="382"/>
749 <line y2="413" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="411"/>
750 <line y2="412" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="412"/>
751 <line y2="443" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="441"/>
752 <line y2="442" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="442"/>
753 <line y2="473" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="471"/>
754 <line y2="472" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="472"/>
755 <line y2="503" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="501"/>
756 <line y2="502" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="502"/>
757 <line y2="533" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="531"/>
758 <line y2="532" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="532"/>
759 <line y2="563" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="561"/>
760 <line y2="562" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="562"/>
761 <line y2="593" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="591"/>
762 <line y2="592" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="592"/>
763 <line y2="623" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="621"/>
764 <line y2="622" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="622"/>
765 <line y2="653" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="651"/>
766 <line y2="652" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="652"/>
767 <line y2="683" fill="none" x1="376" clip-path="url(#clipPath1)" x2="376" y1="681"/>
768 <line y2="682" fill="none" x1="375" clip-path="url(#clipPath1)" x2="377" y1="682"/>
769 <line y2="-37" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="-39"/>
770 <line y2="-38" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="-38"/>
771 <line y2="-7" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="-9"/>
772 <line y2="-8" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="-8"/>
773 <line y2="23" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="21"/>
774 <line y2="22" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="22"/>
775 <line y2="53" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="51"/>
776 <line y2="52" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="52"/>
777 <line y2="83" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="81"/>
778 <line y2="82" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="82"/>
779 <line y2="113" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="111"/>
780 <line y2="112" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="112"/>
781 <line y2="143" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="141"/>
782 <line y2="142" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="142"/>
783 <line y2="173" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="171"/>
784 <line y2="172" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="172"/>
785 <line y2="203" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="201"/>
786 <line y2="202" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="202"/>
787 <line y2="233" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="231"/>
788 <line y2="232" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="232"/>
789 <line y2="263" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="261"/>
790 <line y2="262" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="262"/>
791 <line y2="293" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="291"/>
792 <line y2="292" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="292"/>
793 <line y2="323" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="321"/>
794 <line y2="322" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="322"/>
795 <line y2="353" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="351"/>
796 <line y2="352" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="352"/>
797 <line y2="383" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="381"/>
798 <line y2="382" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="382"/>
799 <line y2="413" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="411"/>
800 <line y2="412" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="412"/>
801 <line y2="443" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="441"/>
802 <line y2="442" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="442"/>
803 <line y2="473" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="471"/>
804 <line y2="472" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="472"/>
805 <line y2="503" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="501"/>
806 <line y2="502" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="502"/>
807 <line y2="533" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="531"/>
808 <line y2="532" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="532"/>
809 <line y2="563" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="561"/>
810 <line y2="562" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="562"/>
811 <line y2="593" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="591"/>
812 <line y2="592" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="592"/>
813 <line y2="623" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="621"/>
814 <line y2="622" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="622"/>
815 <line y2="653" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="651"/>
816 <line y2="652" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="652"/>
817 <line y2="683" fill="none" x1="406" clip-path="url(#clipPath1)" x2="406" y1="681"/>
818 <line y2="682" fill="none" x1="405" clip-path="url(#clipPath1)" x2="407" y1="682"/>
819 <line y2="-37" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="-39"/>
820 <line y2="-38" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="-38"/>
821 <line y2="-7" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="-9"/>
822 <line y2="-8" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="-8"/>
823 <line y2="23" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="21"/>
824 <line y2="22" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="22"/>
825 <line y2="53" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="51"/>
826 <line y2="52" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="52"/>
827 <line y2="83" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="81"/>
828 <line y2="82" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="82"/>
829 <line y2="113" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="111"/>
830 <line y2="112" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="112"/>
831 <line y2="143" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="141"/>
832 <line y2="142" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="142"/>
833 <line y2="173" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="171"/>
834 <line y2="172" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="172"/>
835 <line y2="203" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="201"/>
836 <line y2="202" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="202"/>
837 <line y2="233" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="231"/>
838 <line y2="232" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="232"/>
839 <line y2="263" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="261"/>
840 <line y2="262" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="262"/>
841 <line y2="293" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="291"/>
842 <line y2="292" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="292"/>
843 <line y2="323" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="321"/>
844 <line y2="322" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="322"/>
845 <line y2="353" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="351"/>
846 <line y2="352" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="352"/>
847 <line y2="383" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="381"/>
848 <line y2="382" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="382"/>
849 <line y2="413" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="411"/>
850 <line y2="412" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="412"/>
851 <line y2="443" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="441"/>
852 <line y2="442" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="442"/>
853 <line y2="473" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="471"/>
854 <line y2="472" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="472"/>
855 <line y2="503" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="501"/>
856 <line y2="502" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="502"/>
857 <line y2="533" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="531"/>
858 <line y2="532" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="532"/>
859 <line y2="563" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="561"/>
860 <line y2="562" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="562"/>
861 <line y2="593" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="591"/>
862 <line y2="592" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="592"/>
863 <line y2="623" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="621"/>
864 <line y2="622" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="622"/>
865 <line y2="653" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="651"/>
866 <line y2="652" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="652"/>
867 <line y2="683" fill="none" x1="436" clip-path="url(#clipPath1)" x2="436" y1="681"/>
868 <line y2="682" fill="none" x1="435" clip-path="url(#clipPath1)" x2="437" y1="682"/>
869 <line y2="-37" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="-39"/>
870 <line y2="-38" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="-38"/>
871 <line y2="-7" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="-9"/>
872 <line y2="-8" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="-8"/>
873 <line y2="23" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="21"/>
874 <line y2="22" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="22"/>
875 <line y2="53" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="51"/>
876 <line y2="52" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="52"/>
877 <line y2="83" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="81"/>
878 <line y2="82" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="82"/>
879 <line y2="113" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="111"/>
880 <line y2="112" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="112"/>
881 <line y2="143" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="141"/>
882 <line y2="142" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="142"/>
883 <line y2="173" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="171"/>
884 <line y2="172" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="172"/>
885 <line y2="203" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="201"/>
886 <line y2="202" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="202"/>
887 <line y2="233" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="231"/>
888 <line y2="232" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="232"/>
889 <line y2="263" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="261"/>
890 <line y2="262" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="262"/>
891 <line y2="293" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="291"/>
892 <line y2="292" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="292"/>
893 <line y2="323" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="321"/>
894 <line y2="322" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="322"/>
895 <line y2="353" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="351"/>
896 <line y2="352" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="352"/>
897 <line y2="383" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="381"/>
898 <line y2="382" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="382"/>
899 <line y2="413" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="411"/>
900 <line y2="412" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="412"/>
901 <line y2="443" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="441"/>
902 <line y2="442" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="442"/>
903 <line y2="473" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="471"/>
904 <line y2="472" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="472"/>
905 <line y2="503" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="501"/>
906 <line y2="502" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="502"/>
907 <line y2="533" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="531"/>
908 <line y2="532" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="532"/>
909 <line y2="563" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="561"/>
910 <line y2="562" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="562"/>
911 <line y2="593" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="591"/>
912 <line y2="592" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="592"/>
913 <line y2="623" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="621"/>
914 <line y2="622" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="622"/>
915 <line y2="653" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="651"/>
916 <line y2="652" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="652"/>
917 <line y2="683" fill="none" x1="466" clip-path="url(#clipPath1)" x2="466" y1="681"/>
918 <line y2="682" fill="none" x1="465" clip-path="url(#clipPath1)" x2="467" y1="682"/>
919 <line y2="-37" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="-39"/>
920 <line y2="-38" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="-38"/>
921 <line y2="-7" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="-9"/>
922 <line y2="-8" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="-8"/>
923 <line y2="23" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="21"/>
924 <line y2="22" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="22"/>
925 <line y2="53" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="51"/>
926 <line y2="52" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="52"/>
927 <line y2="83" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="81"/>
928 <line y2="82" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="82"/>
929 <line y2="113" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="111"/>
930 <line y2="112" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="112"/>
931 <line y2="143" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="141"/>
932 <line y2="142" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="142"/>
933 <line y2="173" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="171"/>
934 <line y2="172" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="172"/>
935 <line y2="203" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="201"/>
936 <line y2="202" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="202"/>
937 <line y2="233" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="231"/>
938 <line y2="232" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="232"/>
939 <line y2="263" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="261"/>
940 <line y2="262" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="262"/>
941 <line y2="293" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="291"/>
942 <line y2="292" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="292"/>
943 <line y2="323" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="321"/>
944 <line y2="322" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="322"/>
945 <line y2="353" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="351"/>
946 <line y2="352" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="352"/>
947 <line y2="383" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="381"/>
948 <line y2="382" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="382"/>
949 <line y2="413" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="411"/>
950 <line y2="412" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="412"/>
951 <line y2="443" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="441"/>
952 <line y2="442" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="442"/>
953 <line y2="473" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="471"/>
954 <line y2="472" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="472"/>
955 <line y2="503" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="501"/>
956 <line y2="502" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="502"/>
957 <line y2="533" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="531"/>
958 <line y2="532" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="532"/>
959 <line y2="563" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="561"/>
960 <line y2="562" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="562"/>
961 <line y2="593" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="591"/>
962 <line y2="592" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="592"/>
963 <line y2="623" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="621"/>
964 <line y2="622" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="622"/>
965 <line y2="653" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="651"/>
966 <line y2="652" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="652"/>
967 <line y2="683" fill="none" x1="496" clip-path="url(#clipPath1)" x2="496" y1="681"/>
968 <line y2="682" fill="none" x1="495" clip-path="url(#clipPath1)" x2="497" y1="682"/>
969 <line y2="-37" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="-39"/>
970 <line y2="-38" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="-38"/>
971 <line y2="-7" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="-9"/>
972 <line y2="-8" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="-8"/>
973 <line y2="23" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="21"/>
974 <line y2="22" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="22"/>
975 <line y2="53" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="51"/>
976 <line y2="52" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="52"/>
977 <line y2="83" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="81"/>
978 <line y2="82" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="82"/>
979 <line y2="113" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="111"/>
980 <line y2="112" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="112"/>
981 <line y2="143" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="141"/>
982 <line y2="142" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="142"/>
983 <line y2="173" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="171"/>
984 <line y2="172" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="172"/>
985 <line y2="203" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="201"/>
986 <line y2="202" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="202"/>
987 <line y2="233" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="231"/>
988 <line y2="232" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="232"/>
989 <line y2="263" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="261"/>
990 <line y2="262" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="262"/>
991 <line y2="293" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="291"/>
992 <line y2="292" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="292"/>
993 <line y2="323" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="321"/>
994 <line y2="322" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="322"/>
995 <line y2="353" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="351"/>
996 <line y2="352" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="352"/>
997 <line y2="383" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="381"/>
998 <line y2="382" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="382"/>
999 <line y2="413" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="411"/>
1000 <line y2="412" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="412"/>
1001 <line y2="443" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="441"/>
1002 <line y2="442" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="442"/>
1003 <line y2="473" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="471"/>
1004 <line y2="472" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="472"/>
1005 <line y2="503" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="501"/>
1006 <line y2="502" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="502"/>
1007 <line y2="533" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="531"/>
1008 <line y2="532" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="532"/>
1009 <line y2="563" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="561"/>
1010 <line y2="562" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="562"/>
1011 <line y2="593" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="591"/>
1012 <line y2="592" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="592"/>
1013 <line y2="623" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="621"/>
1014 <line y2="622" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="622"/>
1015 <line y2="653" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="651"/>
1016 <line y2="652" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="652"/>
1017 <line y2="683" fill="none" x1="526" clip-path="url(#clipPath1)" x2="526" y1="681"/>
1018 <line y2="682" fill="none" x1="525" clip-path="url(#clipPath1)" x2="527" y1="682"/>
1019 <line y2="-37" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="-39"/>
1020 <line y2="-38" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="-38"/>
1021 <line y2="-7" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="-9"/>
1022 <line y2="-8" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="-8"/>
1023 <line y2="23" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="21"/>
1024 <line y2="22" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="22"/>
1025 <line y2="53" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="51"/>
1026 <line y2="52" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="52"/>
1027 <line y2="83" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="81"/>
1028 <line y2="82" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="82"/>
1029 <line y2="113" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="111"/>
1030 <line y2="112" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="112"/>
1031 <line y2="143" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="141"/>
1032 <line y2="142" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="142"/>
1033 <line y2="173" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="171"/>
1034 <line y2="172" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="172"/>
1035 <line y2="203" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="201"/>
1036 <line y2="202" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="202"/>
1037 <line y2="233" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="231"/>
1038 <line y2="232" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="232"/>
1039 <line y2="263" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="261"/>
1040 <line y2="262" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="262"/>
1041 <line y2="293" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="291"/>
1042 <line y2="292" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="292"/>
1043 <line y2="323" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="321"/>
1044 <line y2="322" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="322"/>
1045 <line y2="353" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="351"/>
1046 <line y2="352" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="352"/>
1047 <line y2="383" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="381"/>
1048 <line y2="382" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="382"/>
1049 <line y2="413" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="411"/>
1050 <line y2="412" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="412"/>
1051 <line y2="443" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="441"/>
1052 <line y2="442" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="442"/>
1053 <line y2="473" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="471"/>
1054 <line y2="472" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="472"/>
1055 <line y2="503" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="501"/>
1056 <line y2="502" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="502"/>
1057 <line y2="533" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="531"/>
1058 <line y2="532" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="532"/>
1059 <line y2="563" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="561"/>
1060 <line y2="562" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="562"/>
1061 <line y2="593" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="591"/>
1062 <line y2="592" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="592"/>
1063 <line y2="623" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="621"/>
1064 <line y2="622" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="622"/>
1065 <line y2="653" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="651"/>
1066 <line y2="652" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="652"/>
1067 <line y2="683" fill="none" x1="556" clip-path="url(#clipPath1)" x2="556" y1="681"/>
1068 <line y2="682" fill="none" x1="555" clip-path="url(#clipPath1)" x2="557" y1="682"/>
1069 <line y2="-37" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="-39"/>
1070 <line y2="-38" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="-38"/>
1071 <line y2="-7" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="-9"/>
1072 <line y2="-8" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="-8"/>
1073 <line y2="23" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="21"/>
1074 <line y2="22" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="22"/>
1075 <line y2="53" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="51"/>
1076 <line y2="52" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="52"/>
1077 <line y2="83" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="81"/>
1078 <line y2="82" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="82"/>
1079 <line y2="113" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="111"/>
1080 <line y2="112" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="112"/>
1081 <line y2="143" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="141"/>
1082 <line y2="142" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="142"/>
1083 <line y2="173" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="171"/>
1084 <line y2="172" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="172"/>
1085 <line y2="203" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="201"/>
1086 <line y2="202" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="202"/>
1087 <line y2="233" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="231"/>
1088 <line y2="232" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="232"/>
1089 <line y2="263" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="261"/>
1090 <line y2="262" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="262"/>
1091 <line y2="293" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="291"/>
1092 <line y2="292" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="292"/>
1093 <line y2="323" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="321"/>
1094 <line y2="322" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="322"/>
1095 <line y2="353" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="351"/>
1096 <line y2="352" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="352"/>
1097 <line y2="383" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="381"/>
1098 <line y2="382" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="382"/>
1099 <line y2="413" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="411"/>
1100 <line y2="412" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="412"/>
1101 <line y2="443" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="441"/>
1102 <line y2="442" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="442"/>
1103 <line y2="473" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="471"/>
1104 <line y2="472" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="472"/>
1105 <line y2="503" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="501"/>
1106 <line y2="502" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="502"/>
1107 <line y2="533" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="531"/>
1108 <line y2="532" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="532"/>
1109 <line y2="563" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="561"/>
1110 <line y2="562" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="562"/>
1111 <line y2="593" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="591"/>
1112 <line y2="592" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="592"/>
1113 <line y2="623" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="621"/>
1114 <line y2="622" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="622"/>
1115 <line y2="653" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="651"/>
1116 <line y2="652" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="652"/>
1117 <line y2="683" fill="none" x1="586" clip-path="url(#clipPath1)" x2="586" y1="681"/>
1118 <line y2="682" fill="none" x1="585" clip-path="url(#clipPath1)" x2="587" y1="682"/>
1119 <line y2="-37" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="-39"/>
1120 <line y2="-38" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="-38"/>
1121 <line y2="-7" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="-9"/>
1122 <line y2="-8" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="-8"/>
1123 <line y2="23" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="21"/>
1124 <line y2="22" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="22"/>
1125 <line y2="53" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="51"/>
1126 <line y2="52" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="52"/>
1127 <line y2="83" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="81"/>
1128 <line y2="82" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="82"/>
1129 <line y2="113" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="111"/>
1130 <line y2="112" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="112"/>
1131 <line y2="143" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="141"/>
1132 <line y2="142" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="142"/>
1133 <line y2="173" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="171"/>
1134 <line y2="172" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="172"/>
1135 <line y2="203" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="201"/>
1136 <line y2="202" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="202"/>
1137 <line y2="233" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="231"/>
1138 <line y2="232" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="232"/>
1139 <line y2="263" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="261"/>
1140 <line y2="262" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="262"/>
1141 <line y2="293" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="291"/>
1142 <line y2="292" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="292"/>
1143 <line y2="323" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="321"/>
1144 <line y2="322" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="322"/>
1145 <line y2="353" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="351"/>
1146 <line y2="352" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="352"/>
1147 <line y2="383" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="381"/>
1148 <line y2="382" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="382"/>
1149 <line y2="413" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="411"/>
1150 <line y2="412" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="412"/>
1151 <line y2="443" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="441"/>
1152 <line y2="442" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="442"/>
1153 <line y2="473" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="471"/>
1154 <line y2="472" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="472"/>
1155 <line y2="503" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="501"/>
1156 <line y2="502" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="502"/>
1157 <line y2="533" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="531"/>
1158 <line y2="532" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="532"/>
1159 <line y2="563" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="561"/>
1160 <line y2="562" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="562"/>
1161 <line y2="593" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="591"/>
1162 <line y2="592" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="592"/>
1163 <line y2="623" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="621"/>
1164 <line y2="622" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="622"/>
1165 <line y2="653" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="651"/>
1166 <line y2="652" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="652"/>
1167 <line y2="683" fill="none" x1="616" clip-path="url(#clipPath1)" x2="616" y1="681"/>
1168 <line y2="682" fill="none" x1="615" clip-path="url(#clipPath1)" x2="617" y1="682"/>
1169 <line y2="-37" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="-39"/>
1170 <line y2="-38" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="-38"/>
1171 <line y2="-7" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="-9"/>
1172 <line y2="-8" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="-8"/>
1173 <line y2="23" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="21"/>
1174 <line y2="22" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="22"/>
1175 <line y2="53" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="51"/>
1176 <line y2="52" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="52"/>
1177 <line y2="83" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="81"/>
1178 <line y2="82" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="82"/>
1179 <line y2="113" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="111"/>
1180 <line y2="112" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="112"/>
1181 <line y2="143" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="141"/>
1182 <line y2="142" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="142"/>
1183 <line y2="173" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="171"/>
1184 <line y2="172" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="172"/>
1185 <line y2="203" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="201"/>
1186 <line y2="202" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="202"/>
1187 <line y2="233" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="231"/>
1188 <line y2="232" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="232"/>
1189 <line y2="263" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="261"/>
1190 <line y2="262" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="262"/>
1191 <line y2="293" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="291"/>
1192 <line y2="292" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="292"/>
1193 <line y2="323" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="321"/>
1194 <line y2="322" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="322"/>
1195 <line y2="353" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="351"/>
1196 <line y2="352" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="352"/>
1197 <line y2="383" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="381"/>
1198 <line y2="382" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="382"/>
1199 <line y2="413" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="411"/>
1200 <line y2="412" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="412"/>
1201 <line y2="443" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="441"/>
1202 <line y2="442" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="442"/>
1203 <line y2="473" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="471"/>
1204 <line y2="472" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="472"/>
1205 <line y2="503" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="501"/>
1206 <line y2="502" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="502"/>
1207 <line y2="533" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="531"/>
1208 <line y2="532" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="532"/>
1209 <line y2="563" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="561"/>
1210 <line y2="562" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="562"/>
1211 <line y2="593" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="591"/>
1212 <line y2="592" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="592"/>
1213 <line y2="623" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="621"/>
1214 <line y2="622" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="622"/>
1215 <line y2="653" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="651"/>
1216 <line y2="652" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="652"/>
1217 <line y2="683" fill="none" x1="646" clip-path="url(#clipPath1)" x2="646" y1="681"/>
1218 <line y2="682" fill="none" x1="645" clip-path="url(#clipPath1)" x2="647" y1="682"/>
1219 <line y2="-37" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="-39"/>
1220 <line y2="-38" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="-38"/>
1221 <line y2="-7" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="-9"/>
1222 <line y2="-8" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="-8"/>
1223 <line y2="23" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="21"/>
1224 <line y2="22" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="22"/>
1225 <line y2="53" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="51"/>
1226 <line y2="52" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="52"/>
1227 <line y2="83" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="81"/>
1228 <line y2="82" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="82"/>
1229 <line y2="113" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="111"/>
1230 <line y2="112" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="112"/>
1231 <line y2="143" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="141"/>
1232 <line y2="142" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="142"/>
1233 <line y2="173" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="171"/>
1234 <line y2="172" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="172"/>
1235 <line y2="203" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="201"/>
1236 <line y2="202" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="202"/>
1237 <line y2="233" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="231"/>
1238 <line y2="232" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="232"/>
1239 <line y2="263" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="261"/>
1240 <line y2="262" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="262"/>
1241 <line y2="293" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="291"/>
1242 <line y2="292" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="292"/>
1243 <line y2="323" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="321"/>
1244 <line y2="322" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="322"/>
1245 <line y2="353" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="351"/>
1246 <line y2="352" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="352"/>
1247 <line y2="383" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="381"/>
1248 <line y2="382" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="382"/>
1249 <line y2="413" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="411"/>
1250 <line y2="412" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="412"/>
1251 <line y2="443" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="441"/>
1252 <line y2="442" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="442"/>
1253 <line y2="473" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="471"/>
1254 <line y2="472" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="472"/>
1255 <line y2="503" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="501"/>
1256 <line y2="502" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="502"/>
1257 <line y2="533" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="531"/>
1258 <line y2="532" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="532"/>
1259 <line y2="563" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="561"/>
1260 <line y2="562" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="562"/>
1261 <line y2="593" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="591"/>
1262 <line y2="592" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="592"/>
1263 <line y2="623" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="621"/>
1264 <line y2="622" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="622"/>
1265 <line y2="653" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="651"/>
1266 <line y2="652" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="652"/>
1267 <line y2="683" fill="none" x1="676" clip-path="url(#clipPath1)" x2="676" y1="681"/>
1268 <line y2="682" fill="none" x1="675" clip-path="url(#clipPath1)" x2="677" y1="682"/>
1269 <line y2="-37" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="-39"/>
1270 <line y2="-38" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="-38"/>
1271 <line y2="-7" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="-9"/>
1272 <line y2="-8" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="-8"/>
1273 <line y2="23" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="21"/>
1274 <line y2="22" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="22"/>
1275 <line y2="53" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="51"/>
1276 <line y2="52" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="52"/>
1277 <line y2="83" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="81"/>
1278 <line y2="82" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="82"/>
1279 <line y2="113" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="111"/>
1280 <line y2="112" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="112"/>
1281 <line y2="143" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="141"/>
1282 <line y2="142" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="142"/>
1283 <line y2="173" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="171"/>
1284 <line y2="172" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="172"/>
1285 <line y2="203" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="201"/>
1286 <line y2="202" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="202"/>
1287 <line y2="233" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="231"/>
1288 <line y2="232" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="232"/>
1289 <line y2="263" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="261"/>
1290 <line y2="262" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="262"/>
1291 <line y2="293" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="291"/>
1292 <line y2="292" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="292"/>
1293 <line y2="323" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="321"/>
1294 <line y2="322" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="322"/>
1295 <line y2="353" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="351"/>
1296 <line y2="352" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="352"/>
1297 <line y2="383" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="381"/>
1298 <line y2="382" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="382"/>
1299 <line y2="413" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="411"/>
1300 <line y2="412" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="412"/>
1301 <line y2="443" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="441"/>
1302 <line y2="442" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="442"/>
1303 <line y2="473" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="471"/>
1304 <line y2="472" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="472"/>
1305 <line y2="503" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="501"/>
1306 <line y2="502" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="502"/>
1307 <line y2="533" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="531"/>
1308 <line y2="532" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="532"/>
1309 <line y2="563" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="561"/>
1310 <line y2="562" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="562"/>
1311 <line y2="593" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="591"/>
1312 <line y2="592" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="592"/>
1313 <line y2="623" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="621"/>
1314 <line y2="622" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="622"/>
1315 <line y2="653" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="651"/>
1316 <line y2="652" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="652"/>
1317 <line y2="683" fill="none" x1="706" clip-path="url(#clipPath1)" x2="706" y1="681"/>
1318 <line y2="682" fill="none" x1="705" clip-path="url(#clipPath1)" x2="707" y1="682"/>
1319 <line y2="-37" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="-39"/>
1320 <line y2="-38" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="-38"/>
1321 <line y2="-7" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="-9"/>
1322 <line y2="-8" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="-8"/>
1323 <line y2="23" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="21"/>
1324 <line y2="22" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="22"/>
1325 <line y2="53" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="51"/>
1326 <line y2="52" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="52"/>
1327 <line y2="83" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="81"/>
1328 <line y2="82" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="82"/>
1329 <line y2="113" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="111"/>
1330 <line y2="112" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="112"/>
1331 <line y2="143" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="141"/>
1332 <line y2="142" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="142"/>
1333 <line y2="173" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="171"/>
1334 <line y2="172" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="172"/>
1335 <line y2="203" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="201"/>
1336 <line y2="202" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="202"/>
1337 <line y2="233" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="231"/>
1338 <line y2="232" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="232"/>
1339 <line y2="263" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="261"/>
1340 <line y2="262" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="262"/>
1341 <line y2="293" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="291"/>
1342 <line y2="292" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="292"/>
1343 <line y2="323" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="321"/>
1344 <line y2="322" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="322"/>
1345 <line y2="353" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="351"/>
1346 <line y2="352" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="352"/>
1347 <line y2="383" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="381"/>
1348 <line y2="382" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="382"/>
1349 <line y2="413" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="411"/>
1350 <line y2="412" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="412"/>
1351 <line y2="443" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="441"/>
1352 <line y2="442" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="442"/>
1353 <line y2="473" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="471"/>
1354 <line y2="472" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="472"/>
1355 <line y2="503" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="501"/>
1356 <line y2="502" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="502"/>
1357 <line y2="533" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="531"/>
1358 <line y2="532" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="532"/>
1359 <line y2="563" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="561"/>
1360 <line y2="562" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="562"/>
1361 <line y2="593" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="591"/>
1362 <line y2="592" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="592"/>
1363 <line y2="623" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="621"/>
1364 <line y2="622" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="622"/>
1365 <line y2="653" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="651"/>
1366 <line y2="652" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="652"/>
1367 <line y2="683" fill="none" x1="736" clip-path="url(#clipPath1)" x2="736" y1="681"/>
1368 <line y2="682" fill="none" x1="735" clip-path="url(#clipPath1)" x2="737" y1="682"/>
1369 <line y2="-37" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="-39"/>
1370 <line y2="-38" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="-38"/>
1371 <line y2="-7" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="-9"/>
1372 <line y2="-8" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="-8"/>
1373 <line y2="23" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="21"/>
1374 <line y2="22" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="22"/>
1375 <line y2="53" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="51"/>
1376 <line y2="52" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="52"/>
1377 <line y2="83" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="81"/>
1378 <line y2="82" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="82"/>
1379 <line y2="113" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="111"/>
1380 <line y2="112" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="112"/>
1381 <line y2="143" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="141"/>
1382 <line y2="142" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="142"/>
1383 <line y2="173" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="171"/>
1384 <line y2="172" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="172"/>
1385 <line y2="203" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="201"/>
1386 <line y2="202" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="202"/>
1387 <line y2="233" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="231"/>
1388 <line y2="232" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="232"/>
1389 <line y2="263" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="261"/>
1390 <line y2="262" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="262"/>
1391 <line y2="293" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="291"/>
1392 <line y2="292" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="292"/>
1393 <line y2="323" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="321"/>
1394 <line y2="322" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="322"/>
1395 <line y2="353" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="351"/>
1396 <line y2="352" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="352"/>
1397 <line y2="383" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="381"/>
1398 <line y2="382" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="382"/>
1399 <line y2="413" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="411"/>
1400 <line y2="412" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="412"/>
1401 <line y2="443" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="441"/>
1402 <line y2="442" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="442"/>
1403 <line y2="473" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="471"/>
1404 <line y2="472" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="472"/>
1405 <line y2="503" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="501"/>
1406 <line y2="502" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="502"/>
1407 <line y2="533" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="531"/>
1408 <line y2="532" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="532"/>
1409 <line y2="563" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="561"/>
1410 <line y2="562" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="562"/>
1411 <line y2="593" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="591"/>
1412 <line y2="592" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="592"/>
1413 <line y2="623" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="621"/>
1414 <line y2="622" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="622"/>
1415 <line y2="653" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="651"/>
1416 <line y2="652" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="652"/>
1417 <line y2="683" fill="none" x1="766" clip-path="url(#clipPath1)" x2="766" y1="681"/>
1418 <line y2="682" fill="none" x1="765" clip-path="url(#clipPath1)" x2="767" y1="682"/>
1419 <line y2="-37" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="-39"/>
1420 <line y2="-38" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="-38"/>
1421 <line y2="-7" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="-9"/>
1422 <line y2="-8" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="-8"/>
1423 <line y2="23" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="21"/>
1424 <line y2="22" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="22"/>
1425 <line y2="53" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="51"/>
1426 <line y2="52" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="52"/>
1427 <line y2="83" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="81"/>
1428 <line y2="82" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="82"/>
1429 <line y2="113" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="111"/>
1430 <line y2="112" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="112"/>
1431 <line y2="143" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="141"/>
1432 <line y2="142" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="142"/>
1433 <line y2="173" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="171"/>
1434 <line y2="172" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="172"/>
1435 <line y2="203" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="201"/>
1436 <line y2="202" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="202"/>
1437 <line y2="233" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="231"/>
1438 <line y2="232" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="232"/>
1439 <line y2="263" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="261"/>
1440 <line y2="262" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="262"/>
1441 <line y2="293" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="291"/>
1442 <line y2="292" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="292"/>
1443 <line y2="323" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="321"/>
1444 <line y2="322" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="322"/>
1445 <line y2="353" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="351"/>
1446 <line y2="352" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="352"/>
1447 <line y2="383" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="381"/>
1448 <line y2="382" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="382"/>
1449 <line y2="413" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="411"/>
1450 <line y2="412" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="412"/>
1451 <line y2="443" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="441"/>
1452 <line y2="442" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="442"/>
1453 <line y2="473" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="471"/>
1454 <line y2="472" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="472"/>
1455 <line y2="503" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="501"/>
1456 <line y2="502" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="502"/>
1457 <line y2="533" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="531"/>
1458 <line y2="532" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="532"/>
1459 <line y2="563" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="561"/>
1460 <line y2="562" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="562"/>
1461 <line y2="593" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="591"/>
1462 <line y2="592" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="592"/>
1463 <line y2="623" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="621"/>
1464 <line y2="622" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="622"/>
1465 <line y2="653" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="651"/>
1466 <line y2="652" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="652"/>
1467 <line y2="683" fill="none" x1="796" clip-path="url(#clipPath1)" x2="796" y1="681"/>
1468 <line y2="682" fill="none" x1="795" clip-path="url(#clipPath1)" x2="797" y1="682"/>
1469 <line y2="-37" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="-39"/>
1470 <line y2="-38" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="-38"/>
1471 <line y2="-7" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="-9"/>
1472 <line y2="-8" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="-8"/>
1473 <line y2="23" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="21"/>
1474 <line y2="22" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="22"/>
1475 <line y2="53" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="51"/>
1476 <line y2="52" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="52"/>
1477 <line y2="83" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="81"/>
1478 <line y2="82" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="82"/>
1479 <line y2="113" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="111"/>
1480 <line y2="112" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="112"/>
1481 <line y2="143" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="141"/>
1482 <line y2="142" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="142"/>
1483 <line y2="173" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="171"/>
1484 <line y2="172" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="172"/>
1485 <line y2="203" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="201"/>
1486 <line y2="202" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="202"/>
1487 <line y2="233" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="231"/>
1488 <line y2="232" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="232"/>
1489 <line y2="263" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="261"/>
1490 <line y2="262" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="262"/>
1491 <line y2="293" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="291"/>
1492 <line y2="292" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="292"/>
1493 <line y2="323" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="321"/>
1494 <line y2="322" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="322"/>
1495 <line y2="353" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="351"/>
1496 <line y2="352" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="352"/>
1497 <line y2="383" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="381"/>
1498 <line y2="382" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="382"/>
1499 <line y2="413" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="411"/>
1500 <line y2="412" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="412"/>
1501 <line y2="443" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="441"/>
1502 <line y2="442" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="442"/>
1503 <line y2="473" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="471"/>
1504 <line y2="472" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="472"/>
1505 <line y2="503" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="501"/>
1506 <line y2="502" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="502"/>
1507 <line y2="533" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="531"/>
1508 <line y2="532" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="532"/>
1509 <line y2="563" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="561"/>
1510 <line y2="562" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="562"/>
1511 <line y2="593" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="591"/>
1512 <line y2="592" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="592"/>
1513 <line y2="623" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="621"/>
1514 <line y2="622" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="622"/>
1515 <line y2="653" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="651"/>
1516 <line y2="652" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="652"/>
1517 <line y2="683" fill="none" x1="826" clip-path="url(#clipPath1)" x2="826" y1="681"/>
1518 <line y2="682" fill="none" x1="825" clip-path="url(#clipPath1)" x2="827" y1="682"/>
1519 <line y2="-37" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="-39"/>
1520 <line y2="-38" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="-38"/>
1521 <line y2="-7" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="-9"/>
1522 <line y2="-8" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="-8"/>
1523 <line y2="23" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="21"/>
1524 <line y2="22" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="22"/>
1525 <line y2="53" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="51"/>
1526 <line y2="52" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="52"/>
1527 <line y2="83" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="81"/>
1528 <line y2="82" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="82"/>
1529 <line y2="113" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="111"/>
1530 <line y2="112" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="112"/>
1531 <line y2="143" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="141"/>
1532 <line y2="142" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="142"/>
1533 <line y2="173" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="171"/>
1534 <line y2="172" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="172"/>
1535 <line y2="203" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="201"/>
1536 <line y2="202" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="202"/>
1537 <line y2="233" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="231"/>
1538 <line y2="232" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="232"/>
1539 <line y2="263" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="261"/>
1540 <line y2="262" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="262"/>
1541 <line y2="293" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="291"/>
1542 <line y2="292" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="292"/>
1543 <line y2="323" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="321"/>
1544 <line y2="322" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="322"/>
1545 <line y2="353" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="351"/>
1546 <line y2="352" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="352"/>
1547 <line y2="383" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="381"/>
1548 <line y2="382" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="382"/>
1549 <line y2="413" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="411"/>
1550 <line y2="412" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="412"/>
1551 <line y2="443" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="441"/>
1552 <line y2="442" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="442"/>
1553 <line y2="473" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="471"/>
1554 <line y2="472" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="472"/>
1555 <line y2="503" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="501"/>
1556 <line y2="502" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="502"/>
1557 <line y2="533" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="531"/>
1558 <line y2="532" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="532"/>
1559 <line y2="563" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="561"/>
1560 <line y2="562" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="562"/>
1561 <line y2="593" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="591"/>
1562 <line y2="592" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="592"/>
1563 <line y2="623" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="621"/>
1564 <line y2="622" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="622"/>
1565 <line y2="653" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="651"/>
1566 <line y2="652" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="652"/>
1567 <line y2="683" fill="none" x1="856" clip-path="url(#clipPath1)" x2="856" y1="681"/>
1568 <line y2="682" fill="none" x1="855" clip-path="url(#clipPath1)" x2="857" y1="682"/>
1569 <line y2="-37" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="-39"/>
1570 <line y2="-38" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="-38"/>
1571 <line y2="-7" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="-9"/>
1572 <line y2="-8" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="-8"/>
1573 <line y2="23" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="21"/>
1574 <line y2="22" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="22"/>
1575 <line y2="53" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="51"/>
1576 <line y2="52" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="52"/>
1577 <line y2="83" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="81"/>
1578 <line y2="82" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="82"/>
1579 <line y2="113" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="111"/>
1580 <line y2="112" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="112"/>
1581 <line y2="143" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="141"/>
1582 <line y2="142" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="142"/>
1583 <line y2="173" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="171"/>
1584 <line y2="172" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="172"/>
1585 <line y2="203" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="201"/>
1586 <line y2="202" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="202"/>
1587 <line y2="233" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="231"/>
1588 <line y2="232" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="232"/>
1589 <line y2="263" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="261"/>
1590 <line y2="262" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="262"/>
1591 <line y2="293" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="291"/>
1592 <line y2="292" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="292"/>
1593 <line y2="323" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="321"/>
1594 <line y2="322" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="322"/>
1595 <line y2="353" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="351"/>
1596 <line y2="352" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="352"/>
1597 <line y2="383" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="381"/>
1598 <line y2="382" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="382"/>
1599 <line y2="413" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="411"/>
1600 <line y2="412" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="412"/>
1601 <line y2="443" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="441"/>
1602 <line y2="442" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="442"/>
1603 <line y2="473" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="471"/>
1604 <line y2="472" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="472"/>
1605 <line y2="503" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="501"/>
1606 <line y2="502" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="502"/>
1607 <line y2="533" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="531"/>
1608 <line y2="532" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="532"/>
1609 <line y2="563" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="561"/>
1610 <line y2="562" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="562"/>
1611 <line y2="593" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="591"/>
1612 <line y2="592" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="592"/>
1613 <line y2="623" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="621"/>
1614 <line y2="622" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="622"/>
1615 <line y2="653" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="651"/>
1616 <line y2="652" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="652"/>
1617 <line y2="683" fill="none" x1="886" clip-path="url(#clipPath1)" x2="886" y1="681"/>
1618 <line y2="682" fill="none" x1="885" clip-path="url(#clipPath1)" x2="887" y1="682"/>
1619 <line y2="-37" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="-39"/>
1620 <line y2="-38" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="-38"/>
1621 <line y2="-7" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="-9"/>
1622 <line y2="-8" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="-8"/>
1623 <line y2="23" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="21"/>
1624 <line y2="22" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="22"/>
1625 <line y2="53" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="51"/>
1626 <line y2="52" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="52"/>
1627 <line y2="83" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="81"/>
1628 <line y2="82" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="82"/>
1629 <line y2="113" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="111"/>
1630 <line y2="112" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="112"/>
1631 <line y2="143" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="141"/>
1632 <line y2="142" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="142"/>
1633 <line y2="173" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="171"/>
1634 <line y2="172" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="172"/>
1635 <line y2="203" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="201"/>
1636 <line y2="202" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="202"/>
1637 <line y2="233" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="231"/>
1638 <line y2="232" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="232"/>
1639 <line y2="263" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="261"/>
1640 <line y2="262" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="262"/>
1641 <line y2="293" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="291"/>
1642 <line y2="292" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="292"/>
1643 <line y2="323" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="321"/>
1644 <line y2="322" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="322"/>
1645 <line y2="353" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="351"/>
1646 <line y2="352" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="352"/>
1647 <line y2="383" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="381"/>
1648 <line y2="382" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="382"/>
1649 <line y2="413" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="411"/>
1650 <line y2="412" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="412"/>
1651 <line y2="443" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="441"/>
1652 <line y2="442" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="442"/>
1653 <line y2="473" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="471"/>
1654 <line y2="472" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="472"/>
1655 <line y2="503" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="501"/>
1656 <line y2="502" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="502"/>
1657 <line y2="533" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="531"/>
1658 <line y2="532" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="532"/>
1659 <line y2="563" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="561"/>
1660 <line y2="562" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="562"/>
1661 <line y2="593" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="591"/>
1662 <line y2="592" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="592"/>
1663 <line y2="623" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="621"/>
1664 <line y2="622" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="622"/>
1665 <line y2="653" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="651"/>
1666 <line y2="652" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="652"/>
1667 <line y2="683" fill="none" x1="916" clip-path="url(#clipPath1)" x2="916" y1="681"/>
1668 <line y2="682" fill="none" x1="915" clip-path="url(#clipPath1)" x2="917" y1="682"/>
1669 <line y2="-37" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="-39"/>
1670 <line y2="-38" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="-38"/>
1671 <line y2="-7" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="-9"/>
1672 <line y2="-8" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="-8"/>
1673 <line y2="23" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="21"/>
1674 <line y2="22" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="22"/>
1675 <line y2="53" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="51"/>
1676 <line y2="52" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="52"/>
1677 <line y2="83" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="81"/>
1678 <line y2="82" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="82"/>
1679 <line y2="113" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="111"/>
1680 <line y2="112" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="112"/>
1681 <line y2="143" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="141"/>
1682 <line y2="142" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="142"/>
1683 <line y2="173" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="171"/>
1684 <line y2="172" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="172"/>
1685 <line y2="203" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="201"/>
1686 <line y2="202" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="202"/>
1687 <line y2="233" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="231"/>
1688 <line y2="232" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="232"/>
1689 <line y2="263" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="261"/>
1690 <line y2="262" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="262"/>
1691 <line y2="293" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="291"/>
1692 <line y2="292" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="292"/>
1693 <line y2="323" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="321"/>
1694 <line y2="322" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="322"/>
1695 <line y2="353" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="351"/>
1696 <line y2="352" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="352"/>
1697 <line y2="383" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="381"/>
1698 <line y2="382" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="382"/>
1699 <line y2="413" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="411"/>
1700 <line y2="412" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="412"/>
1701 <line y2="443" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="441"/>
1702 <line y2="442" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="442"/>
1703 <line y2="473" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="471"/>
1704 <line y2="472" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="472"/>
1705 <line y2="503" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="501"/>
1706 <line y2="502" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="502"/>
1707 <line y2="533" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="531"/>
1708 <line y2="532" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="532"/>
1709 <line y2="563" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="561"/>
1710 <line y2="562" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="562"/>
1711 <line y2="593" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="591"/>
1712 <line y2="592" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="592"/>
1713 <line y2="623" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="621"/>
1714 <line y2="622" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="622"/>
1715 <line y2="653" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="651"/>
1716 <line y2="652" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="652"/>
1717 <line y2="683" fill="none" x1="946" clip-path="url(#clipPath1)" x2="946" y1="681"/>
1718 <line y2="682" fill="none" x1="945" clip-path="url(#clipPath1)" x2="947" y1="682"/>
1719 <line y2="-37" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="-39"/>
1720 <line y2="-38" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="-38"/>
1721 <line y2="-7" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="-9"/>
1722 <line y2="-8" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="-8"/>
1723 <line y2="23" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="21"/>
1724 <line y2="22" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="22"/>
1725 <line y2="53" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="51"/>
1726 <line y2="52" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="52"/>
1727 <line y2="83" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="81"/>
1728 <line y2="82" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="82"/>
1729 <line y2="113" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="111"/>
1730 <line y2="112" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="112"/>
1731 <line y2="143" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="141"/>
1732 <line y2="142" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="142"/>
1733 <line y2="173" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="171"/>
1734 <line y2="172" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="172"/>
1735 <line y2="203" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="201"/>
1736 <line y2="202" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="202"/>
1737 <line y2="233" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="231"/>
1738 <line y2="232" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="232"/>
1739 <line y2="263" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="261"/>
1740 <line y2="262" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="262"/>
1741 <line y2="293" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="291"/>
1742 <line y2="292" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="292"/>
1743 <line y2="323" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="321"/>
1744 <line y2="322" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="322"/>
1745 <line y2="353" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="351"/>
1746 <line y2="352" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="352"/>
1747 <line y2="383" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="381"/>
1748 <line y2="382" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="382"/>
1749 <line y2="413" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="411"/>
1750 <line y2="412" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="412"/>
1751 <line y2="443" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="441"/>
1752 <line y2="442" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="442"/>
1753 <line y2="473" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="471"/>
1754 <line y2="472" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="472"/>
1755 <line y2="503" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="501"/>
1756 <line y2="502" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="502"/>
1757 <line y2="533" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="531"/>
1758 <line y2="532" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="532"/>
1759 <line y2="563" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="561"/>
1760 <line y2="562" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="562"/>
1761 <line y2="593" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="591"/>
1762 <line y2="592" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="592"/>
1763 <line y2="623" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="621"/>
1764 <line y2="622" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="622"/>
1765 <line y2="653" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="651"/>
1766 <line y2="652" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="652"/>
1767 <line y2="683" fill="none" x1="976" clip-path="url(#clipPath1)" x2="976" y1="681"/>
1768 <line y2="682" fill="none" x1="975" clip-path="url(#clipPath1)" x2="977" y1="682"/>
1769 </g>
1770 <g fill="rgb(153,102,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,102,0)">
1771 <rect x="775.3543" y="344.2005" clip-path="url(#clipPath2)" width="102" rx="4" ry="4" height="50" stroke="none"/>
1772 </g>
1773 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1774 <rect x="775.3543" y="344.2005" clip-path="url(#clipPath2)" fill="none" width="102" rx="4" ry="4" height="50"/>
1775 </g>
1776 <g fill="rgb(204,153,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,153,0)">
1777 <rect x="771.1969" y="339.3212" clip-path="url(#clipPath2)" width="102" rx="4" ry="4" height="50" stroke="none"/>
1778 </g>
1779 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1780 <rect x="771.1969" y="339.3212" clip-path="url(#clipPath2)" fill="none" width="102" rx="4" ry="4" height="50"/>
1781 </g>
1782 <g fill="rgb(153,102,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,102,0)">
1783 <rect x="139.0432" y="484.1153" clip-path="url(#clipPath2)" width="60.6" rx="4" ry="4" height="37.6881" stroke="none"/>
1784 </g>
1785 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1786 <rect x="139.0432" y="484.1153" clip-path="url(#clipPath2)" fill="none" width="60.6" rx="4" ry="4" height="37.6881"/>
1787 </g>
1788 <g fill="rgb(153,102,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,102,0)">
1789 <rect x="286.1808" y="308.193" clip-path="url(#clipPath2)" width="63" rx="4" ry="4" height="324.1319" stroke="none"/>
1790 </g>
1791 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1792 <rect x="286.1808" y="308.193" clip-path="url(#clipPath2)" fill="none" width="63" rx="4" ry="4" height="324.1319"/>
1793 </g>
1794 <g fill="rgb(204,153,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,153,0)">
1795 <rect x="281.1228" y="302.2783" clip-path="url(#clipPath2)" width="63" rx="4" ry="4" height="324.1319" stroke="none"/>
1796 </g>
1797 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1798 <rect x="281.1228" y="302.2783" clip-path="url(#clipPath2)" fill="none" width="63" rx="4" ry="4" height="324.1319"/>
1799 </g>
1800 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1801 <rect x="399" y="173.5" clip-path="url(#clipPath2)" width="102" rx="4" ry="4" height="169.5" stroke="none"/>
1802 </g>
1803 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1804 <rect x="399" y="173.5" clip-path="url(#clipPath2)" fill="none" width="102" rx="4" ry="4" height="169.5"/>
1805 <text x="439.6055" xml:space="preserve" y="255.4199" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mix</text>
1806 <text x="415.5322" xml:space="preserve" y="269.3887" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Compositor</text>
1807 </g>
1808 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1809 <rect x="399" y="570.6719" clip-path="url(#clipPath2)" width="102" rx="4" ry="4" height="50" stroke="none"/>
1810 </g>
1811 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1812 <rect x="399" y="570.6719" clip-path="url(#clipPath2)" fill="none" width="102" rx="4" ry="4" height="50"/>
1813 <text x="432.9463" xml:space="preserve" y="592.8419" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Audio</text>
1814 <text x="433.4473" xml:space="preserve" y="606.8106" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mixer</text>
1815 </g>
1816 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1817 <rect x="276" y="296.54" clip-path="url(#clipPath2)" width="63" rx="4" ry="4" height="324.1319" stroke="none"/>
1818 </g>
1819 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1820 <rect x="276" y="296.54" clip-path="url(#clipPath2)" fill="none" width="63" rx="4" ry="4" height="324.1319"/>
1821 </g>
1822 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" font-family="sans-serif" transform="matrix(-0,-1,1,-0,224.5156,323.4478)" stroke-linecap="butt">
1823 <text x="2" xml:space="preserve" y="13.1387" clip-path="url(#clipPath3)" stroke="none">DeMux</text>
1824 </g>
1825 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1826 <rect x="656.75" y="260.02" clip-path="url(#clipPath2)" width="102" rx="4" ry="4" height="50" stroke="none"/>
1827 </g>
1828 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1829 <rect x="656.75" y="260.02" clip-path="url(#clipPath2)" fill="none" width="102" rx="4" ry="4" height="50"/>
1830 <text x="697.3555" xml:space="preserve" y="275.2055" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mix</text>
1831 <text x="683.4102" xml:space="preserve" y="289.1743" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Blinding</text>
1832 <text x="673.2822" xml:space="preserve" y="303.143" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Compositor</text>
1833 </g>
1834 <g fill="rgb(204,255,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,255,204)">
1835 <path d="M92.4 243.7571 L210.3 243.7571 L223.4 268.7571 L210.3 293.7571 L92.4 293.7571 L105.5 268.7571 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1836 </g>
1837 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1838 <path fill="none" d="M92.4 243.7571 L210.3 243.7571 L223.4 268.7571 L210.3 293.7571 L92.4 293.7571 L105.5 268.7571 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1839 <text x="121.9732" xml:space="preserve" y="258.9427" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Background</text>
1840 <text x="118.3111" xml:space="preserve" y="272.9114" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Video Source</text>
1841 <text x="136.7916" xml:space="preserve" y="286.8802" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:16000</text>
1842 </g>
1843 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1844 <rect x="656.75" y="570.6719" clip-path="url(#clipPath2)" width="102" rx="4" ry="4" height="50" stroke="none"/>
1845 </g>
1846 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1847 <rect x="656.75" y="570.6719" clip-path="url(#clipPath2)" fill="none" width="102" rx="4" ry="4" height="50"/>
1848 <text x="690.6963" xml:space="preserve" y="585.8575" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Audio</text>
1849 <text x="683.4102" xml:space="preserve" y="599.8262" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Blinding</text>
1850 <text x="691.1973" xml:space="preserve" y="613.795" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mixer</text>
1851 </g>
1852 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1853 <circle r="18.5" clip-path="url(#clipPath2)" cx="707.75" cy="468.7119" stroke="none"/>
1854 </g>
1855 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1856 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="707.75" cy="468.7119"/>
1857 <text x="695.2197" xml:space="preserve" y="472.8662" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mux</text>
1858 </g>
1859 <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,204,255)">
1860 <path d="M869.75 443.7119 L987.65 443.7119 L1000.75 468.7119 L987.65 493.7119 L869.75 493.7119 L882.85 468.7119 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1861 </g>
1862 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1863 <path fill="none" d="M869.75 443.7119 L987.65 443.7119 L1000.75 468.7119 L987.65 493.7119 L869.75 493.7119 L882.85 468.7119 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1864 <text x="924.8555" xml:space="preserve" y="458.8975" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mix</text>
1865 <text x="921.0908" xml:space="preserve" y="472.8662" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Live </text>
1866 <text x="914.1416" xml:space="preserve" y="486.835" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:15000</text>
1867 </g>
1868 <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,204,255)">
1869 <path d="M525.6528 441.3279 L643.5528 441.3279 L656.6528 466.3279 L643.5528 491.3279 L525.6528 491.3279 L538.7528 466.3279 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1870 </g>
1871 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1872 <path fill="none" d="M525.6528 441.3279 L643.5528 441.3279 L656.6528 466.3279 L643.5528 491.3279 L525.6528 491.3279 L538.7528 466.3279 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1873 <text x="580.7582" xml:space="preserve" y="456.5134" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mix</text>
1874 <text x="567.56" xml:space="preserve" y="470.4822" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Preview</text>
1875 <text x="570.0444" xml:space="preserve" y="484.4509" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:11100</text>
1876 </g>
1877 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1878 <circle r="18.5" clip-path="url(#clipPath2)" cx="480" cy="466.3279" stroke="none"/>
1879 </g>
1880 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1881 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="480" cy="466.3279"/>
1882 <text x="467.4697" xml:space="preserve" y="470.4822" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mux</text>
1883 </g>
1884 <g fill="rgb(204,255,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,255,204)">
1885 <path d="M525.6528 632.6738 L643.5528 632.6738 L656.6528 657.6738 L643.5528 682.6738 L525.6528 682.6738 L538.7528 657.6738 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1886 </g>
1887 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1888 <path fill="none" d="M525.6528 632.6738 L643.5528 632.6738 L656.6528 657.6738 L643.5528 682.6738 L525.6528 682.6738 L538.7528 657.6738 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1889 <text x="566.8129" xml:space="preserve" y="647.8593" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Blinding</text>
1890 <text x="551.4526" xml:space="preserve" y="661.8281" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Audio Source</text>
1891 <text x="570.0444" xml:space="preserve" y="675.7968" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:18000</text>
1892 </g>
1893 <g fill="rgb(0,102,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,102,204)">
1894 <path d="M875.5125 692.1179 L993.4125 692.1179 L1006.5125 717.1179 L993.4125 742.1179 L875.5125 742.1179 L888.6125 717.1179 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1895 </g>
1896 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1897 <path fill="none" d="M875.5125 692.1179 L993.4125 692.1179 L1006.5125 717.1179 L993.4125 742.1179 L875.5125 742.1179 L888.6125 717.1179 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1898 </g>
1899 <g fill="rgb(102,153,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(102,153,255)">
1900 <path d="M872.7694 686.5858 L990.6694 686.5858 L1003.7694 711.5858 L990.6694 736.5858 L872.7694 736.5858 L885.8694 711.5858 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1901 </g>
1902 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1903 <path fill="none" d="M872.7694 686.5858 L990.6694 686.5858 L1003.7694 711.5858 L990.6694 736.5858 L872.7694 736.5858 L885.8694 711.5858 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1904 </g>
1905 <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,204,255)">
1906 <path d="M869.75 680.9463 L987.65 680.9463 L1000.75 705.9463 L987.65 730.9463 L869.75 730.9463 L882.85 705.9463 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1907 </g>
1908 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1909 <path fill="none" d="M869.75 680.9463 L987.65 680.9463 L1000.75 705.9463 L987.65 730.9463 L869.75 730.9463 L882.85 705.9463 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1910 <text x="911.3848" xml:space="preserve" y="696.1318" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Sources</text>
1911 <text x="904.8662" xml:space="preserve" y="710.1006" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Recording</text>
1912 <text x="908.4199" xml:space="preserve" y="724.0693" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:13000...</text>
1913 </g>
1914 <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,204,255)">
1915 <path d="M525.6528 503.942 L643.5528 503.942 L656.6528 528.942 L643.5528 553.942 L525.6528 553.942 L538.7528 528.942 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1916 </g>
1917 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1918 <path fill="none" d="M525.6528 503.942 L643.5528 503.942 L656.6528 528.942 L643.5528 553.942 L525.6528 553.942 L538.7528 528.942 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1919 <text x="580.7582" xml:space="preserve" y="519.1275" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mix</text>
1920 <text x="560.769" xml:space="preserve" y="533.0963" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Recording</text>
1921 <text x="570.0444" xml:space="preserve" y="547.065" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:11000</text>
1922 </g>
1923 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1924 <circle r="18.5" clip-path="url(#clipPath2)" cx="420" cy="528.9419" stroke="none"/>
1925 </g>
1926 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1927 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="420" cy="528.9419"/>
1928 <text x="407.4697" xml:space="preserve" y="533.0963" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mux</text>
1929 </g>
1930 <g fill="rgb(153,102,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,102,0)">
1931 <circle r="18.5" clip-path="url(#clipPath2)" cx="168.4257" cy="601.7604" stroke="none"/>
1932 </g>
1933 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1934 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="168.4257" cy="601.7604"/>
1935 </g>
1936 <g fill="rgb(204,153,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,153,0)">
1937 <circle r="18.5" clip-path="url(#clipPath2)" cx="164.4192" cy="597.9028" stroke="none"/>
1938 </g>
1939 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1940 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="164.4192" cy="597.9028"/>
1941 </g>
1942 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1943 <circle r="18.5" clip-path="url(#clipPath2)" cx="160.5432" cy="593.5834" stroke="none"/>
1944 </g>
1945 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1946 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="160.5432" cy="593.5834"/>
1947 <text x="148.0129" xml:space="preserve" y="597.7377" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mux</text>
1948 </g>
1949 <g fill="rgb(204,255,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,255,204)">
1950 <path d="M560.8528 773.16 L615.3928 773.16 L621.4528 784.08 L615.3928 795 L560.8528 795 L566.9128 784.08 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1951 </g>
1952 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1953 <path fill="none" d="M560.8528 773.16 L615.3928 773.16 L621.4528 784.08 L615.3928 795 L560.8528 795 L566.9128 784.08 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1954 <text x="575.6166" xml:space="preserve" y="788.2343" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Input</text>
1955 </g>
1956 <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,204,255)">
1957 <path d="M639.6828 773.16 L694.2228 773.16 L700.2828 784.08 L694.2228 795 L639.6828 795 L645.7428 784.08 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1958 </g>
1959 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1960 <path fill="none" d="M639.6828 773.16 L694.2228 773.16 L700.2828 784.08 L694.2228 795 L639.6828 795 L645.7428 784.08 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1961 <text x="649.141" xml:space="preserve" y="788.2343" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Output</text>
1962 </g>
1963 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1964 <rect x="449.7" y="380.1398" clip-path="url(#clipPath2)" width="60.6" rx="4" ry="4" height="37.6881" stroke="none"/>
1965 </g>
1966 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1967 <rect x="449.7" y="380.1398" clip-path="url(#clipPath2)" fill="none" width="60.6" rx="4" ry="4" height="37.6881"/>
1968 <text x="463.8574" xml:space="preserve" y="403.1382" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Scale</text>
1969 </g>
1970 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1971 <rect x="766.5339" y="334.371" clip-path="url(#clipPath2)" width="102" rx="4" ry="4" height="50" stroke="none"/>
1972 </g>
1973 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1974 <rect x="766.5339" y="334.371" clip-path="url(#clipPath2)" fill="none" width="102" rx="4" ry="4" height="50"/>
1975 <text x="793.6687" xml:space="preserve" y="349.5565" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Sources</text>
1976 <text x="793.1941" xml:space="preserve" y="363.5253" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Blinding</text>
1977 <text x="783.0662" xml:space="preserve" y="377.494" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Compositor</text>
1978 </g>
1979 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1980 <path d="M563.875 359.371 L578.875 344.371 L593.875 359.371 L578.875 374.371 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1981 </g>
1982 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1983 <path fill="none" d="M563.875 359.371 L578.875 344.371 L593.875 359.371 L578.875 374.371 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1984 <text x="543.1504" xml:space="preserve" y="335.5409" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">live source?</text>
1985 </g>
1986 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
1987 <path d="M730.2828 780 L745.2828 765 L760.2828 780 L745.2828 795 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
1988 </g>
1989 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1990 <path fill="none" d="M730.2828 780 L745.2828 765 L760.2828 780 L745.2828 795 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
1991 <text x="731.3257" xml:space="preserve" y="756.1699" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">filter</text>
1992 </g>
1993 <g fill="rgb(204,153,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,153,0)">
1994 <rect x="134.7488" y="479.3761" clip-path="url(#clipPath2)" width="60.6" rx="4" ry="4" height="37.6881" stroke="none"/>
1995 </g>
1996 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
1997 <rect x="134.7488" y="479.3761" clip-path="url(#clipPath2)" fill="none" width="60.6" rx="4" ry="4" height="37.6881"/>
1998 </g>
1999 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
2000 <rect x="130.2432" y="474.4609" clip-path="url(#clipPath2)" width="60.6" rx="4" ry="4" height="37.6881" stroke="none"/>
2001 </g>
2002 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2003 <rect x="130.2432" y="474.4609" clip-path="url(#clipPath2)" fill="none" width="60.6" rx="4" ry="4" height="37.6881"/>
2004 <text x="144.4006" xml:space="preserve" y="497.4592" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Scale</text>
2005 </g>
2006 <g fill="rgb(153,102,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,102,0)">
2007 <circle r="18.5" clip-path="url(#clipPath2)" cx="457.8825" cy="715.7655" stroke="none"/>
2008 </g>
2009 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2010 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="457.8825" cy="715.7655"/>
2011 </g>
2012 <g fill="rgb(204,153,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,153,0)">
2013 <circle r="18.5" clip-path="url(#clipPath2)" cx="453.876" cy="711.908" stroke="none"/>
2014 </g>
2015 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2016 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="453.876" cy="711.908"/>
2017 </g>
2018 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
2019 <circle r="18.5" clip-path="url(#clipPath2)" cx="450" cy="707.5886" stroke="none"/>
2020 </g>
2021 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2022 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="450" cy="707.5886"/>
2023 <text x="437.4697" xml:space="preserve" y="711.7429" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mux</text>
2024 </g>
2025 <g fill="rgb(153,102,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,102,0)">
2026 <circle r="18.5" clip-path="url(#clipPath2)" cx="825.4164" cy="603.8489" stroke="none"/>
2027 </g>
2028 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2029 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="825.4164" cy="603.8489"/>
2030 </g>
2031 <g fill="rgb(204,153,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,153,0)">
2032 <circle r="18.5" clip-path="url(#clipPath2)" cx="821.4099" cy="599.9913" stroke="none"/>
2033 </g>
2034 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2035 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="821.4099" cy="599.9913"/>
2036 </g>
2037 <g fill="rgb(255,204,0)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(255,204,0)">
2038 <circle r="18.5" clip-path="url(#clipPath2)" cx="817.5339" cy="595.6719" stroke="none"/>
2039 </g>
2040 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2041 <circle fill="none" r="18.5" clip-path="url(#clipPath2)" cx="817.5339" cy="595.6719"/>
2042 <text x="805.0037" xml:space="preserve" y="599.8262" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Mux</text>
2043 </g>
2044 <g fill="rgb(0,102,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,102,204)">
2045 <path d="M875.3373 581.8205 L993.2373 581.8205 L1006.3376 606.8205 L993.2373 631.8205 L875.3373 631.8205 L888.4374 606.8205 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2046 </g>
2047 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2048 <path fill="none" d="M875.3373 581.8205 L993.2373 581.8205 L1006.3376 606.8205 L993.2373 631.8205 L875.3373 631.8205 L888.4374 606.8205 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2049 </g>
2050 <g fill="rgb(102,153,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(102,153,255)">
2051 <path d="M872.5943 576.2883 L990.4943 576.2883 L1003.5942 601.2883 L990.4943 626.2883 L872.5943 626.2883 L885.6943 601.2883 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2052 </g>
2053 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2054 <path fill="none" d="M872.5943 576.2883 L990.4943 576.2883 L1003.5942 601.2883 L990.4943 626.2883 L872.5943 626.2883 L885.6943 601.2883 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2055 </g>
2056 <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,204,255)">
2057 <path d="M869.75 570.6719 L987.65 570.6719 L1000.75 595.6719 L987.65 620.6719 L869.75 620.6719 L882.85 595.6719 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2058 </g>
2059 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2060 <path fill="none" d="M869.75 570.6719 L987.65 570.6719 L1000.75 595.6719 L987.65 620.6719 L869.75 620.6719 L882.85 595.6719 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2061 <text x="911.3848" xml:space="preserve" y="585.8575" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Sources</text>
2062 <text x="922.998" xml:space="preserve" y="599.8262" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Live</text>
2063 <text x="908.4199" xml:space="preserve" y="613.795" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:15001...</text>
2064 </g>
2065 <g fill="rgb(0,102,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,102,204)">
2066 <path d="M100.8057 682.5886 L218.7056 682.5886 L231.8058 707.5886 L218.7056 732.5886 L100.8057 732.5886 L113.9057 707.5886 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2067 </g>
2068 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2069 <path fill="none" d="M100.8057 682.5886 L218.7056 682.5886 L231.8058 707.5886 L218.7056 732.5886 L100.8057 732.5886 L113.9057 707.5886 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2070 </g>
2071 <g fill="rgb(102,153,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(102,153,255)">
2072 <path d="M98.0626 677.0565 L215.9626 677.0565 L229.0627 702.0565 L215.9626 727.0565 L98.0626 727.0565 L111.1626 702.0565 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2073 </g>
2074 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2075 <path fill="none" d="M98.0626 677.0565 L215.9626 677.0565 L229.0627 702.0565 L215.9626 727.0565 L98.0626 727.0565 L111.1626 702.0565 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2076 </g>
2077 <g fill="rgb(153,204,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(153,204,255)">
2078 <path d="M95.0432 671.417 L212.9432 671.417 L226.0432 696.417 L212.9432 721.417 L95.0432 721.417 L108.1431 696.417 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2079 </g>
2080 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2081 <path fill="none" d="M95.0432 671.417 L212.9432 671.417 L226.0432 696.417 L212.9432 721.417 L95.0432 721.417 L108.1431 696.417 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2082 <text x="136.678" xml:space="preserve" y="686.6025" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Sources</text>
2083 <text x="136.9504" xml:space="preserve" y="700.5712" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Preview</text>
2084 <text x="133.7131" xml:space="preserve" y="714.54" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:13100...</text>
2085 </g>
2086 <g fill="rgb(0,153,51)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,153,51)">
2087 <path d="M98.1626 334.9288 L216.0626 334.9288 L229.1626 359.9288 L216.0626 384.9288 L98.1626 384.9288 L111.2626 359.9288 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2088 </g>
2089 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2090 <path fill="none" d="M98.1626 334.9288 L216.0626 334.9288 L229.1626 359.9288 L216.0626 384.9288 L98.1626 384.9288 L111.2626 359.9288 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2091 </g>
2092 <g fill="rgb(0,204,102)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,204,102)">
2093 <path d="M95.4195 329.3967 L213.3195 329.3967 L226.4195 354.3967 L213.3195 379.3966 L95.4195 379.3966 L108.5195 354.3967 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2094 </g>
2095 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2096 <path fill="none" d="M95.4195 329.3967 L213.3195 329.3967 L226.4195 354.3967 L213.3195 379.3966 L95.4195 379.3966 L108.5195 354.3967 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2097 </g>
2098 <g fill="rgb(204,255,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,255,204)">
2099 <path d="M92.4001 323.7572 L210.3 323.7572 L223.4001 348.7572 L210.3 373.7572 L92.4001 373.7572 L105.5 348.7572 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2100 </g>
2101 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2102 <path fill="none" d="M92.4001 323.7572 L210.3 323.7572 L223.4001 348.7572 L210.3 373.7572 L92.4001 373.7572 L105.5 348.7572 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2103 <text x="134.0348" xml:space="preserve" y="345.927" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Sources</text>
2104 <text x="131.0699" xml:space="preserve" y="359.8958" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:10000...</text>
2105 </g>
2106 <g fill="rgb(0,153,51)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,153,51)">
2107 <path d="M95.1431 185 L213.0431 185 L226.1431 210 L213.0431 235 L95.1431 235 L108.2431 210 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2108 </g>
2109 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2110 <path fill="none" d="M95.1431 185 L213.0431 185 L226.1431 210 L213.0431 235 L95.1431 235 L108.2431 210 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2111 </g>
2112 <g fill="rgb(0,204,102)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,204,102)">
2113 <path d="M92.4 179.4678 L210.3 179.4678 L223.4 204.4678 L210.3 229.4679 L92.4 229.4679 L105.5 204.4678 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2114 </g>
2115 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2116 <path fill="none" d="M92.4 179.4678 L210.3 179.4678 L223.4 204.4678 L210.3 229.4679 L92.4 229.4679 L105.5 204.4678 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2117 </g>
2118 <g fill="rgb(204,255,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,255,204)">
2119 <path d="M89.3805 173.8284 L207.2805 173.8284 L220.3806 198.8284 L207.2805 223.8284 L89.3805 223.8284 L102.4805 198.8284 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2120 </g>
2121 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2122 <path fill="none" d="M89.3805 173.8284 L207.2805 173.8284 L220.3806 198.8284 L207.2805 223.8284 L89.3805 223.8284 L102.4805 198.8284 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2123 <text x="131.5544" xml:space="preserve" y="189.0139" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Overlay</text>
2124 <text x="131.0153" xml:space="preserve" y="202.9827" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Sources</text>
2125 <text x="122.0475" xml:space="preserve" y="216.9514" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">(local files)</text>
2126 </g>
2127 <g fill="rgb(0,153,51)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,153,51)">
2128 <path d="M520.3121 185 L638.2121 185 L651.3121 209.9999 L638.2121 234.9999 L520.3121 234.9999 L533.4121 209.9999 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2129 </g>
2130 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2131 <path fill="none" d="M520.3121 185 L638.2121 185 L651.3121 209.9999 L638.2121 234.9999 L520.3121 234.9999 L533.4121 209.9999 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2132 </g>
2133 <g fill="rgb(0,204,102)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(0,204,102)">
2134 <path d="M517.569 179.4678 L635.4691 179.4678 L648.569 204.4678 L635.4691 229.4677 L517.569 229.4677 L530.6691 204.4678 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2135 </g>
2136 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2137 <path fill="none" d="M517.569 179.4678 L635.4691 179.4678 L648.569 204.4678 L635.4691 229.4677 L517.569 229.4677 L530.6691 204.4678 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2138 </g>
2139 <g fill="rgb(204,255,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke="rgb(204,255,204)">
2140 <path d="M514.5496 173.8282 L632.4496 173.8282 L645.5496 198.8283 L632.4496 223.8282 L514.5496 223.8282 L527.6497 198.8283 Z" fill-rule="evenodd" clip-path="url(#clipPath2)" stroke="none"/>
2141 </g>
2142 <g text-rendering="geometricPrecision" stroke-miterlimit="1.45" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-74,-158)" stroke-linecap="butt">
2143 <path fill="none" d="M514.5496 173.8282 L632.4496 173.8282 L645.5496 198.8283 L632.4496 223.8282 L514.5496 223.8282 L527.6497 198.8283 Z" fill-rule="evenodd" clip-path="url(#clipPath2)"/>
2144 <text x="553.8023" xml:space="preserve" y="189.0139" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Blinding </text>
2145 <text x="537.3345" xml:space="preserve" y="202.9827" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Video Sources</text>
2146 <text x="553.2193" xml:space="preserve" y="216.9514" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">:17000...</text>
2147 <path fill="none" d="M338.9675 318 L386.0107 318" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2148 <path fill="blue" d="M399.0107 318 L379.5107 309.875 L384.3857 318 L379.5107 326.125 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2149 <path fill="none" d="M338.9815 595.6719 L386.0172 595.6719" stroke-width="6" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2150 <path fill="rgb(255,102,0)" d="M399.0172 595.6719 L379.5172 587.5469 L384.3922 595.6719 L379.5172 603.7969 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2151 <path fill="none" d="M501.0184 285.02 L646.7485 285.02" stroke-width="3" clip-path="url(#clipPath2)" stroke="blue"/>
2152 <path fill="blue" d="M656.7485 285.02 L641.7485 278.77 L645.4985 285.02 L641.7485 291.27 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2153 <path fill="none" d="M223.3802 268.7571 L389.0211 268.7571" stroke-width="3" clip-path="url(#clipPath2)" stroke="blue"/>
2154 <path fill="blue" d="M399.0211 268.7571 L384.0211 262.5071 L387.7711 268.7571 L384.0211 275.0071 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2155 <path fill="none" d="M501.0025 595.6719 L646.7446 595.6719" stroke-width="3" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2156 <path fill="rgb(255,102,0)" d="M656.7446 595.6719 L641.7446 589.4219 L645.4946 595.6719 L641.7446 601.9219 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2157 <path fill="none" d="M707.75 309.9856 L707.75 440.2119" stroke-width="3" clip-path="url(#clipPath2)" stroke="blue"/>
2158 <path fill="blue" d="M707.75 450.2119 L714 435.2119 L707.75 438.9619 L701.5 435.2119 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2159 <path fill="none" d="M707.75 570.6581 L707.75 497.2119" stroke-width="3" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2160 <path fill="rgb(255,102,0)" d="M707.75 487.2119 L701.5 502.2119 L707.75 498.4619 L714 502.2119 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2161 <path fill="none" d="M726.25 468.7119 L872.8724 468.7119" stroke-width="3" clip-path="url(#clipPath2)"/>
2162 <path d="M882.8724 468.7119 L867.8724 462.4619 L871.6224 468.7119 L867.8724 474.9619 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2163 <path fill="none" d="M480 570.6881 L480 494.8279" stroke-width="3" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2164 <path fill="rgb(255,102,0)" d="M480 484.8279 L473.75 499.8279 L480 496.0779 L486.25 499.8279 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2165 <path fill="none" d="M498.5 466.3279 L528.7189 466.3279" stroke-width="3" clip-path="url(#clipPath2)"/>
2166 <path d="M538.7189 466.3279 L523.7189 460.0779 L527.4689 466.3279 L523.7189 472.5779 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2167 <path fill="none" d="M656.6533 657.6738 L707.75 657.6738 L707.75 630.6483" stroke-width="3" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2168 <path fill="rgb(255,102,0)" d="M707.75 620.6483 L701.5 635.6483 L707.75 631.8983 L714 635.6483 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2169 <path fill="none" d="M420 570.6835 L420 557.442" stroke-width="3" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2170 <path fill="rgb(255,102,0)" d="M420 547.442 L413.75 562.442 L420 558.692 L426.25 562.442 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2171 <path fill="none" d="M420 342.9649 L420 500.442" stroke-width="3" clip-path="url(#clipPath2)" stroke="blue"/>
2172 <path fill="blue" d="M420 510.442 L426.25 495.442 L420 499.192 L413.75 495.442 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2173 <path fill="none" d="M438.5 528.942 L475 528.942 M485 528.942 L528.7157 528.942" stroke-width="3" clip-path="url(#clipPath2)"/>
2174 <path d="M538.7157 528.942 L523.7157 522.692 L527.4657 528.942 L523.7157 535.192 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2175 <path fill="none" d="M276.0319 593.5834 L192.0432 593.5834" stroke-width="6" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2176 <path fill="rgb(255,102,0)" d="M179.0432 593.5834 L198.5432 601.7084 L193.6682 593.5834 L198.5432 585.4584 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2177 <path fill="none" d="M288.7376 788.16 L316.6528 788.16" stroke-width="3" clip-path="url(#clipPath2)"/>
2178 <path d="M326.6528 788.16 L311.6528 781.91 L315.4028 788.16 L311.6528 794.41 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2179 <text x="297.4647" xml:space="preserve" y="781.3299" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">A/V</text>
2180 <path fill="none" d="M353.068 788.16 L380.9832 788.16" stroke-width="3" clip-path="url(#clipPath2)" stroke="blue"/>
2181 <path fill="blue" d="M390.9832 788.16 L375.9832 781.91 L379.7332 788.16 L375.9832 794.41 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2182 <text x="355.0832" xml:space="preserve" y="781.3299" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Video</text>
2183 <path fill="none" d="M420.6616 788.16 L448.5768 788.16" stroke-width="3" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2184 <path fill="rgb(255,102,0)" d="M458.5768 788.16 L443.5768 781.91 L447.3268 788.16 L443.5768 794.41 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2185 <text x="422.5655" xml:space="preserve" y="781.3299" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Audio</text>
2186 <path fill="none" d="M492.1528 788.16 L520.068 788.16" stroke-width="3" clip-path="url(#clipPath2)" stroke="green"/>
2187 <path fill="green" d="M530.068 788.16 L515.068 781.91 L518.818 788.16 L515.068 794.41 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2188 <text x="492.3194" xml:space="preserve" y="781.3299" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none">Image</text>
2189 <path fill="none" d="M480 342.976 L480 370.1414" stroke-width="3" clip-path="url(#clipPath2)" stroke="blue"/>
2190 <path fill="blue" d="M480 380.1414 L486.25 365.1414 L480 368.8914 L473.75 365.1414 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2191 <path fill="none" d="M480 417.8257 L480 437.8279" stroke-width="3" clip-path="url(#clipPath2)" stroke="blue"/>
2192 <path fill="blue" d="M480 447.8279 L486.25 432.8279 L480 436.5779 L473.75 432.8279 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2193 <path fill="none" d="M338.9932 359.371 L415 359.371 M425 359.371 L475 359.371 M485 359.371 L550.8531 359.371" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2194 <path fill="blue" d="M563.8531 359.371 L544.3531 351.246 L549.2281 359.371 L544.3531 367.496 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2195 <path fill="none" d="M593.8786 359.371 L702.75 359.371 M712.75 359.371 L753.5071 359.371" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2196 <path fill="blue" d="M766.5071 359.371 L747.0071 351.246 L751.8821 359.371 L747.0071 367.496 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2197 <path fill="none" d="M275.993 499.4549 L203.8016 499.4549" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2198 <path fill="blue" d="M190.8016 499.4549 L210.3016 507.5799 L205.4266 499.4549 L210.3016 491.3299 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2199 <path fill="none" d="M160.5432 512.1561 L160.5432 562.0834" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2200 <path fill="blue" d="M160.5432 575.0834 L168.6682 555.5834 L160.5432 560.4584 L152.4182 555.5834 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2201 <path fill="none" d="M307.5 620.6938 L307.5 707.5886 L418.5 707.5886" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2202 <path fill="blue" d="M431.5 707.5886 L412 699.4636 L416.875 707.5886 L412 715.7136 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2203 <path fill="none" d="M450 620.6729 L450 679.0886" stroke-width="3" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2204 <path fill="rgb(255,102,0)" d="M450 689.0886 L456.25 674.0886 L450 677.8386 L443.75 674.0886 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2205 <path fill="none" d="M468.4999 707.5246 L869.7643 706.1378" stroke-width="6" clip-path="url(#clipPath2)"/>
2206 <path d="M882.7642 706.0928 L863.2363 698.0353 L868.1393 706.1434 L863.2924 714.2852 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2207 <path fill="none" d="M758.7556 595.6719 L789.0339 595.6719" stroke-width="3" clip-path="url(#clipPath2)" stroke="rgb(255,102,0)"/>
2208 <path fill="rgb(255,102,0)" d="M799.0339 595.6719 L784.0339 589.4219 L787.7839 595.6719 L784.0339 601.9219 Z" stroke-width="3" clip-path="url(#clipPath2)" stroke="none"/>
2209 <path fill="none" d="M817.5339 384.3799 L817.5339 463.7119 M817.5339 473.7119 L817.5339 564.1719" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2210 <path fill="blue" d="M817.5339 577.1719 L825.6589 557.6719 L817.5339 562.5469 L809.4089 557.6719 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2211 <path fill="none" d="M836.0339 595.6719 L869.8806 595.6719" stroke-width="6" clip-path="url(#clipPath2)"/>
2212 <path d="M882.8806 595.6719 L863.3806 587.5469 L868.2556 595.6719 L863.3806 603.7969 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2213 <path fill="none" d="M160.5432 612.0834 L160.5432 658.4216" stroke-width="6" clip-path="url(#clipPath2)"/>
2214 <path d="M160.5432 671.4216 L168.6682 651.9216 L160.5432 656.7966 L152.4182 651.9216 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2215 <path fill="none" d="M223.3669 348.7571 L262.9658 348.7571" stroke-width="6" clip-path="url(#clipPath2)"/>
2216 <path d="M275.9658 348.7571 L256.4658 340.6321 L261.3408 348.7571 L256.4658 356.8821 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2217 <path fill="none" d="M220.285 199.0688 L385.9957 199.6779" stroke-width="6" clip-path="url(#clipPath2)" stroke="green"/>
2218 <path fill="green" d="M398.9956 199.7257 L379.5256 191.5291 L384.3707 199.6719 L379.4659 207.779 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2219 <path fill="none" d="M645.4794 198.7379 L817.5339 198.5 L817.5339 321.3527" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2220 <path fill="blue" d="M817.5339 334.3527 L825.6589 314.8527 L817.5339 319.7277 L809.4089 314.8527 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2221 <path fill="none" d="M645.5521 198.8284 L707.75 198.8284 L707.75 246.979" stroke-width="6" clip-path="url(#clipPath2)" stroke="blue"/>
2222 <path fill="blue" d="M707.75 259.979 L715.875 240.479 L707.75 245.354 L699.625 240.479 Z" stroke-width="6" clip-path="url(#clipPath2)" stroke="none"/>
2223 </g>
2224 </g>
2225 </svg>
Binary diff not shown
0 import sys
1 sys.path.insert(0, '..')
2 sys.path.insert(0, '.')
99
1010 parser = argparse.ArgumentParser(description='Voctocore')
1111 parser.add_argument('-v', '--verbose', action='count', default=0,
12 help="Also print INFO and DEBUG messages.")
12 help="Set verbosity level by using -v, -vv or -vvv.")
1313
1414 parser.add_argument('-c', '--color',
1515 action='store',
2323 parser.add_argument('-i', '--ini-file', action='store',
2424 help="Load a custom config.ini-File")
2525
26 parser.add_argument('-p', '--pipeline', action='store_true',
27 help="Generate text files of pipelines")
28
29 parser.add_argument('-n', '--no-bins', action='store_true',
30 help="Do not use gstreamer bins")
31
32 parser.add_argument('-d', '--dot', action='store_true',
33 help="Generate DOT files of pipelines into directory given in environment variable GST_DEBUG_DUMP_DOT_DIR")
34
35 parser.add_argument('-D', '--gst-debug-details', action='store', default=1,
36 help="Set details in dot graph. GST_DEBUG_DETAILS must be a combination the following values: 1 = show caps-name on edges, 2 = show caps-details on edges, 4 = show modified parameters on elements, 8 = show element states, 16 = show full element parameter values even if they are very long. Default: 15 = show all the typical details that one might want (15=1+2+4+8)")
37
38 parser.add_argument('-g', '--gstreamer-log', action='count', default=0,
39 help="Log gstreamer messages into voctocore log (Set log level by using -g, -gg or -ggg).")
40
2641 Args = parser.parse_args()
0 #!/usr/bin/env python3
01 import logging
12 from configparser import NoOptionError, NoSectionError
23
3 from gi.repository import Gst
4
54 from lib.config import Config
6 from lib.clock import Clock
75 from lib.errors.configuration_error import ConfigurationError
6 from lib.args import Args
87
98
109 class AudioMix(object):
10
1111 def __init__(self):
1212 self.log = logging.getLogger('AudioMix')
1313
14 self.caps = Config.get('mix', 'audiocaps')
15 self.names = Config.getlist('mix', 'sources')
16 self.log.info('Configuring Mixer for %u Sources', len(self.names))
14 self.audio_streams = Config.getAudioStreams()
15 self.streams = self.audio_streams.get_stream_names()
16 # initialize all sources to silent
17 self.volumes = [1.0] * len(self.streams)
1718
18 # initialize all sources to silent
19 self.volumes = [0.0] * len(self.names)
20 is_configured = False
19 self.log.info('Configuring audio mixer for %u streams',
20 len(self.streams))
2121
22 # try per-source volume-setting
23 for index, name in enumerate(self.names):
24 section = 'source.{}'.format(name)
25 try:
26 volume = Config.getfloat(section, 'volume')
27 self.log.info('Setting Volume of Source %s to %0.2f',
28 name, volume)
29 self.volumes[index] = volume
30 is_configured = True
31 except (NoSectionError, NoOptionError):
32 pass
22 self.mix_volume = 1.0
3323
34 # try [mix]audiosource shortcut
35 try:
36 name = Config.get('mix', 'audiosource')
37 if is_configured:
38 raise ConfigurationError(
39 'cannot configure [mix]audiosource-shortcut and '
40 '[source.*]volume at the same time')
24 self.bin = "" if Args.no_bins else """
25 bin.(
26 name=AudioMix
27 """
4128
42 if name not in self.names:
43 raise ConfigurationError(
44 'unknown source configured as [mix]audiosource: %s', name)
29 channels = Config.getAudioChannels()
4530
46 index = self.names.index(name)
47 self.log.info('Setting Volume of Source %s to %0.2f', name, 1.0)
48 self.volumes[index] = 1.0
49 is_configured = True
50 except NoOptionError:
51 pass
31 def identity():
32 matrix = [[0.0 for x in range(0, channels)]
33 for x in range(0, channels)]
34 for i in range(0, channels):
35 matrix[i][i] = 1.0
36 return str(matrix).replace("[","<").replace("]",">")
5237
53 if is_configured:
54 self.log.info(
55 'Volume was configured, advising ui not to show a selector')
56 Config.add_section_if_missing('audio')
57 Config.set('audio', 'volumecontrol', 'false')
38 self.bin += """
39 audiomixer
40 name=audiomixer
41 ! queue
42 max-size-time=3000000000
43 name=queue-audiomixer-audiomixmatrix
44 ! audiomixmatrix
45 name=audiomixer-audiomixmatrix
46 in_channels={in_channels}
47 out_channels={out_channels}
48 matrix="{matrix}"
49 ! queue
50 max-size-time=3000000000
51 name=queue-audio-mix
52 ! tee
53 name=audio-mix
54 """.format(in_channels=channels,
55 out_channels=channels,
56 matrix=identity())
5857
59 else:
60 self.log.info('Setting Volume of first Source %s to %0.2f',
61 self.names[0], 1.0)
62 self.volumes[0] = 1.0
58 for stream in self.streams:
59 self.bin += """
60 audio-{stream}.
61 ! queue
62 max-size-time=3000000000
63 name=queue-audio-{stream}
64 ! audiomixer.
65 """.format(stream=stream)
66 self.bin += "" if Args.no_bins else "\n)"
6367
64 pipeline = ""
65 for audiostream in range(0, Config.getint('mix', 'audiostreams')):
66 pipeline += """
67 audiomixer name=mix_{audiostream} !
68 {caps} !
69 queue !
70 tee name=tee_{audiostream}
71
72 tee_{audiostream}. ! queue ! interaudiosink
73 channel=audio_mix_out_stream{audiostream}
74 """.format(
75 caps=self.caps,
76 audiostream=audiostream,
77 )
78
79 if Config.getboolean('previews', 'enabled'):
80 pipeline += """
81 tee_{audiostream}. ! queue ! interaudiosink
82 channel=audio_mix_preview_stream{audiostream}
83 """.format(
84 audiostream=audiostream,
85 )
86
87 if Config.getboolean('stream-blanker', 'enabled'):
88 pipeline += """
89 tee_{audiostream}. ! queue ! interaudiosink
90 channel=audio_mix_stream{audiostream}_stream-blanker
91 """.format(
92 audiostream=audiostream,
93 )
94
95 for idx, name in enumerate(self.names):
96 pipeline += """
97 interaudiosrc
98 channel=audio_{name}_mixer_stream{audiostream} !
99 {caps} !
100 mix_{audiostream}.
101 """.format(
102 name=name,
103 caps=self.caps,
104 audiostream=audiostream,
105 )
106
107 self.log.debug('Creating Mixing-Pipeline:\n%s', pipeline)
108 self.mixingPipeline = Gst.parse_launch(pipeline)
109 self.mixingPipeline.use_clock(Clock)
110
111 self.log.debug('Binding Error & End-of-Stream-Signal '
112 'on Mixing-Pipeline')
113 self.mixingPipeline.bus.add_signal_watch()
114 self.mixingPipeline.bus.connect("message::eos", self.on_eos)
115 self.mixingPipeline.bus.connect("message::error", self.on_error)
116
117 self.log.debug('Initializing Mixer-State')
68 def attach(self, pipeline):
69 self.pipeline = pipeline
11870 self.updateMixerState()
11971
120 self.log.debug('Launching Mixing-Pipeline')
121 self.mixingPipeline.set_state(Gst.State.PLAYING)
72 def __str__(self):
73 return 'AudioMix'
74
75 def isConfigured(self):
76 for v in self.volumes:
77 if v > 0.0:
78 return True
79 return False
12280
12381 def updateMixerState(self):
124 self.log.info('Updating Mixer-State')
82 self.log.info('Updating mixer state')
12583
126 for idx, name in enumerate(self.names):
127 volume = self.volumes[idx]
84 for idx, name in enumerate(self.streams):
85 volume = self.volumes[idx] * self.mix_volume
12886
129 self.log.debug('Setting Mixerpad %u to volume=%0.2f', idx, volume)
130 for audiostream in range(0, Config.getint('mix', 'audiostreams')):
131 mixer = self.mixingPipeline.get_by_name(
132 'mix_{}'.format(audiostream))
133
134 mixerpad = mixer.get_static_pad('sink_%u' % idx)
135 mixerpad.set_property('volume', volume)
87 self.log.debug('Setting stream %s to volume=%0.2f', name, volume)
88 mixer = self.pipeline.get_by_name('audiomixer')
89 mixerpad = mixer.get_static_pad('sink_%d' % idx)
90 mixerpad.set_property('volume', volume)
13691
13792 def setAudioSource(self, source):
138 self.volumes = [float(idx == source) for idx in range(len(self.names))]
93 self.volumes = [float(idx == source)
94 for idx in range(len(self.sources))]
13995 self.updateMixerState()
14096
141 def setAudioSourceVolume(self, source, volume):
142 self.volumes[source] = volume
97 def setAudioSourceVolume(self, stream, volume):
98 if stream == 'mix':
99 self.mix_volume = volume
100 else:
101 self.volumes[stream] = volume
102 self.updateMixerState()
103
104 def setAudioVolume(self, volume):
105 self.mix_volume = volume
143106 self.updateMixerState()
144107
145108 def getAudioVolumes(self):
146109 return self.volumes
147
148 def on_eos(self, bus, message):
149 self.log.debug('Received End-of-Stream-Signal on Mixing-Pipeline')
150
151 def on_error(self, bus, message):
152 self.log.debug('Received Error-Signal on Mixing-Pipeline')
153 (error, debug) = message.parse_error()
154 self.log.debug('Error-Details: #%u: %s', error.code, debug)
0 #!/usr/bin/env python3
1 from lib.tcpmulticonnection import TCPMultiConnection
2 from lib.config import Config
3 from lib.args import Args
4 from gi.repository import Gst
05 import logging
1
2 from gi.repository import Gst
3
4 from lib.config import Config
5 from lib.tcpmulticonnection import TCPMultiConnection
6 from lib.clock import Clock
6 import gi
7 gi.require_version('GstController', '1.0')
78
89
910 class AVPreviewOutput(TCPMultiConnection):
10 def __init__(self, channel, port):
11 self.log = logging.getLogger('AVPreviewOutput[{}]'.format(channel))
11
12 def __init__(self, source, port, use_audio_mix=False, audio_blinded=False):
13 # create logging interface
14 self.log = logging.getLogger('AVPreviewOutput[{}]'.format(source))
15
16 # initialize super
1217 super().__init__(port)
1318
14 self.channel = channel
19 # remember things
20 self.source = source
1521
16 if Config.has_option('previews', 'videocaps'):
17 target_caps = Config.get('previews', 'videocaps')
22 # open bin
23 self.bin = "" if Args.no_bins else """
24 bin.(
25 name=AVPreviewOutput-{source}
26 """.format(source=self.source)
27
28 # video pipeline
29 self.bin += """
30 video-{source}.
31 ! {vcaps}
32 ! queue
33 max-size-time=3000000000
34 name=queue-preview-video-{source}
35 {vpipeline}
36 ! queue
37 max-size-time=3000000000
38 name=queue-mux-preview-{source}
39 ! mux-preview-{source}.
40 """.format(source=self.source,
41 vpipeline=self.construct_video_pipeline(),
42 vcaps=Config.getVideoCaps()
43 )
44
45 # audio pipeline
46 if use_audio_mix or source in Config.getAudioSources(internal=True):
47 self.bin += """
48 {use_audio}audio-{audio_source}{audio_blinded}.
49 ! queue
50 max-size-time=3000000000
51 name=queue-preview-audio-{source}
52 ! audioconvert
53 ! queue
54 max-size-time=3000000000
55 name=queue-mux-preview-audio-{source}
56 ! mux-preview-{source}.
57 """.format(source=self.source,
58 use_audio="" if use_audio_mix else "source-",
59 audio_source="mix" if use_audio_mix else self.source,
60 audio_blinded="-blinded" if audio_blinded else ""
61 )
62
63 # playout pipeline
64 self.bin += """
65 matroskamux
66 name=mux-preview-{source}
67 streamable=true
68 writing-app=Voctomix-AVPreviewOutput
69 ! queue
70 max-size-time=3000000000
71 name=queue-fd-preview-{source}
72 ! multifdsink
73 blocksize=1048576
74 buffers-max=500
75 sync-method=next-keyframe
76 name=fd-preview-{source}
77 """.format(source=self.source)
78
79 # close bin
80 self.bin += "" if Args.no_bins else "\n)\n"
81
82 def audio_channels(self):
83 return Config.getNumAudioStreams()
84
85 def video_channels(self):
86 return 1
87
88 def is_input(self):
89 return False
90
91 def __str__(self):
92 return 'AVPreviewOutput[{}]'.format(self.source)
93
94 def construct_video_pipeline(self):
95 if Config.getPreviewVaapi():
96 return self.construct_vaapi_video_pipeline()
1897 else:
19 target_caps = Config.get('mix', 'videocaps')
98 return self.construct_native_video_pipeline()
2099
21 pipeline = """
22 intervideosrc channel=video_{channel} !
23 {vcaps} !
24 {vpipeline} !
25 queue !
26 mux.
27 """.format(
28 channel=self.channel,
29 vcaps=Config.get('mix', 'videocaps'),
30 vpipeline=self.construct_video_pipeline(target_caps)
31 )
32
33 for audiostream in range(0, Config.getint('mix', 'audiostreams')):
34 pipeline += """
35 interaudiosrc channel=audio_{channel}_stream{audiostream} !
36 {acaps} !
37 queue !
38 mux.
39 """.format(
40 channel=self.channel,
41 acaps=Config.get('mix', 'audiocaps'),
42 audiostream=audiostream,
43 )
44
45 pipeline += """
46 matroskamux
47 name=mux
48 streamable=true
49 writing-app=Voctomix-AVPreviewOutput !
50
51 multifdsink
52 blocksize=1048576
53 buffers-max=500
54 sync-method=next-keyframe
55 name=fd
56 """
57
58 self.log.debug('Creating Output-Pipeline:\n%s', pipeline)
59 self.outputPipeline = Gst.parse_launch(pipeline)
60 self.outputPipeline.use_clock(Clock)
61
62 self.log.debug('Binding Error & End-of-Stream-Signal '
63 'on Output-Pipeline')
64 self.outputPipeline.bus.add_signal_watch()
65 self.outputPipeline.bus.connect("message::eos", self.on_eos)
66 self.outputPipeline.bus.connect("message::error", self.on_error)
67
68 self.log.debug('Launching Output-Pipeline')
69 self.outputPipeline.set_state(Gst.State.PLAYING)
70
71 def construct_video_pipeline(self, target_caps):
72 vaapi_enabled = Config.has_option('previews', 'vaapi')
73 if vaapi_enabled:
74 return self.construct_vaapi_video_pipeline(target_caps)
75
76 else:
77 return self.construct_native_video_pipeline(target_caps)
78
79 def construct_vaapi_video_pipeline(self, target_caps):
100 def construct_vaapi_video_pipeline(self):
101 # https://blogs.igalia.com/vjaquez/2016/04/06/gstreamer-vaapi-1-8-the-codec-split/
80102 if Gst.version() < (1, 8):
81103 vaapi_encoders = {
82104 'h264': 'vaapiencode_h264',
91113 }
92114
93115 vaapi_encoder_options = {
94 'h264': 'rate-control=cqp init-qp=10 '
95 'max-bframes=0 keyframe-period=60',
96 'jpeg': 'vaapiencode_jpeg quality=90'
97 'keyframe-period=0',
98 'mpeg2': 'keyframe-period=60',
116 'h264': """rate-control=cqp
117 init-qp=10
118 max-bframes=0
119 keyframe-period=60""",
120 'jpeg': """quality=90
121 keyframe-period=0""",
122 'mpeg2': "keyframe-period=60",
99123 }
100124
101 encoder = Config.get('previews', 'vaapi')
102 do_deinterlace = Config.getboolean('previews', 'deinterlace')
125 # prepare selectors
126 size = Config.getPreviewResolution()
127 framerate = Config.getPreviewFramerate()
128 vaapi = Config.getPreviewVaapi()
129 denoise = Config.getDenoiseVaapi()
130 scale_method = Config.getScaleMethodVaapi()
103131
104 caps = Gst.Caps.from_string(target_caps)
105 struct = caps.get_structure(0)
106 _, width = struct.get_int('width')
107 _, height = struct.get_int('height')
108 (_, framerate_numerator,
109 framerate_denominator) = struct.get_fraction('framerate')
132 # generate pipeline
133 # we can also force a video format here (format=I420) but this breaks scalling at least on Intel HD3000 therefore it currently removed
134 return """ ! capsfilter
135 caps=video/x-raw,interlace-mode=progressive
136 ! vaapipostproc
137 ! video/x-raw,width={width},height={height},framerate={n}/{d},deinterlace-mode={imode},deinterlace-method=motion-adaptive,denoise={denoise},scale-method={scale_method}
138 ! {encoder}
139 {options}""".format(imode='interlaced' if Config.getDeinterlacePreviews() else 'disabled',
140 width=size[0],
141 height=size[1],
142 encoder=vaapi_encoders[vaapi],
143 options=vaapi_encoder_options[vaapi],
144 n=framerate[0],
145 d=framerate[1],
146 denoise=denoise,
147 scale_method=scale_method,
148 )
110149
111 return '''
112 capsfilter caps=video/x-raw,interlace-mode=progressive !
113 vaapipostproc
114 format=i420
115 deinterlace-mode={imode}
116 deinterlace-method=motion-adaptive
117 width={width}
118 height={height} !
119 capssetter caps=video/x-raw,framerate={n}/{d} !
120 {encoder} {options}
121 '''.format(
122 imode='interlaced' if do_deinterlace else 'disabled',
123 width=width,
124 height=height,
125 encoder=vaapi_encoders[encoder],
126 options=vaapi_encoder_options[encoder],
127 n=framerate_numerator,
128 d=framerate_denominator,
129 )
150 def construct_native_video_pipeline(self):
151 # maybe add deinterlacer
152 if Config.getDeinterlacePreviews():
153 pipeline = """ ! deinterlace
154 mode=interlaced
155 """
156 else:
157 pipeline = ""
130158
131 def construct_native_video_pipeline(self, target_caps):
132 do_deinterlace = Config.getboolean('previews', 'deinterlace')
159 # build rest of the pipeline
160 pipeline += """ ! videorate
161 ! videoscale
162 ! {vcaps}
163 ! jpegenc
164 quality = 90""".format(vcaps=Config.getPreviewCaps())
165 return pipeline
133166
134 if do_deinterlace:
135 pipeline = '''
136 deinterlace mode={imode} !
137 videorate !
138 '''
139 else:
140 pipeline = ''
141
142 pipeline += '''
143 videoscale !
144 {target_caps} !
145 jpegenc quality=90
146 '''
147
148 return pipeline.format(
149 imode='interlaced' if do_deinterlace else 'disabled',
150 target_caps=target_caps,
151 )
167 def attach(self, pipeline):
168 self.pipeline = pipeline
152169
153170 def on_accepted(self, conn, addr):
154171 self.log.debug('Adding fd %u to multifdsink', conn.fileno())
155 fdsink = self.outputPipeline.get_by_name('fd')
172
173 # find fdsink and emit 'add'
174 fdsink = self.pipeline.get_by_name("fd-preview-{}".format(self.source))
156175 fdsink.emit('add', conn.fileno())
157176
177 # catch disconnect
158178 def on_disconnect(multifdsink, fileno):
159179 if fileno == conn.fileno():
160180 self.log.debug('fd %u removed from multifdsink', fileno)
161181 self.close_connection(conn)
162
163182 fdsink.connect('client-fd-removed', on_disconnect)
164183
165 def on_eos(self, bus, message):
166 self.log.debug('Received End-of-Stream-Signal on Output-Pipeline')
167
168 def on_error(self, bus, message):
169 self.log.debug('Received Error-Signal on Output-Pipeline')
170 (error, debug) = message.parse_error()
171 self.log.debug('Error-Details: #%u: %s', error.code, debug)
184 # catch client-removed
185 def on_client_removed(multifdsink, fileno, status):
186 # GST_CLIENT_STATUS_SLOW = 3,
187 if fileno == conn.fileno() and status == 3:
188 self.log.warning('about to remove fd %u from multifdsink '
189 'because it is too slow!', fileno)
190 fdsink.connect('client-removed', on_client_removed)
0 #!/usr/bin/env python3
01 import logging
12
2 from gi.repository import Gst
3
3 from lib.args import Args
44 from lib.config import Config
55 from lib.tcpmulticonnection import TCPMultiConnection
6 from lib.clock import Clock
76
87
98 class AVRawOutput(TCPMultiConnection):
10 def __init__(self, channel, port):
11 self.log = logging.getLogger('AVRawOutput[{}]'.format(channel))
9
10 def __init__(self, source, port, use_audio_mix=False, audio_blinded=False):
11 # create logging interface
12 self.log = logging.getLogger('AVRawOutput[{}]'.format(source))
13
14 # initialize super
1215 super().__init__(port)
1316
14 self.channel = channel
17 # remember things
18 self.source = source
1519
16 pipeline = """
17 intervideosrc channel=video_{channel} !
18 {vcaps} !
19 queue !
20 mux.
21 """.format(
22 channel=self.channel,
23 vcaps=Config.get('mix', 'videocaps'),
20 # open bin
21 self.bin = "" if Args.no_bins else """
22 bin.(
23 name=AVRawOutput-{source}
24 """.format(source=self.source)
25
26 # video pipeline
27 self.bin += """
28 video-{source}.
29 ! {vcaps}
30 ! queue
31 max-size-time=3000000000
32 name=queue-mux-video-{source}
33 ! mux-{source}.
34 """.format(source=self.source,
35 vcaps=Config.getVideoCaps())
36
37 # audio pipeline
38 if use_audio_mix or source in Config.getAudioSources(internal=True):
39 self.bin += """
40 {use_audio}audio-{audio_source}{audio_blinded}.
41 ! queue
42 max-size-time=3000000000
43 name=queue-audio-mix-convert-{source}
44 ! audioconvert
45 ! queue
46 max-size-time=3000000000
47 name=queue-mux-audio-{source}
48 ! mux-{source}.
49 """.format(
50 source=self.source,
51 use_audio="" if use_audio_mix else "source-",
52 audio_source="mix" if use_audio_mix else self.source,
53 audio_blinded="-blinded" if audio_blinded else ""
54 )
55
56 # playout pipeline
57 self.bin += """
58 matroskamux
59 name=mux-{source}
60 streamable=true
61 writing-app=Voctomix-AVRawOutput
62 ! queue
63 max-size-time=3000000000
64 name=queue-fd-{source}
65 ! multifdsink
66 blocksize=1048576
67 buffers-max={buffers_max}
68 sync-method=next-keyframe
69 name=fd-{source}
70 """.format(
71 buffers_max=Config.getOutputBuffers(self.source),
72 source=self.source
2473 )
2574
26 for audiostream in range(0, Config.getint('mix', 'audiostreams')):
27 pipeline += """
28 interaudiosrc channel=audio_{channel}_stream{audiostream} !
29 {acaps} !
30 queue !
31 mux.
32 """.format(
33 channel=self.channel,
34 acaps=Config.get('mix', 'audiocaps'),
35 audiostream=audiostream,
36 )
75 # close bin
76 self.bin += "" if Args.no_bins else "\n)\n"
3777
38 pipeline += """
39 matroskamux
40 name=mux
41 streamable=true
42 writing-app=Voctomix-AVRawOutput !
78 def audio_channels(self):
79 return Config.getNumAudioStreams()
4380
44 multifdsink
45 blocksize=1048576
46 buffers-max={buffers_max}
47 sync-method=next-keyframe
48 name=fd
49 """.format(
50 buffers_max=Config.getint('output-buffers', channel, fallback=500)
51 )
52 self.log.debug('Creating Output-Pipeline:\n%s', pipeline)
53 self.outputPipeline = Gst.parse_launch(pipeline)
54 self.outputPipeline.use_clock(Clock)
81 def video_channels(self):
82 return 1
5583
56 self.log.debug('Binding Error & End-of-Stream-Signal '
57 'on Output-Pipeline')
58 self.outputPipeline.bus.add_signal_watch()
59 self.outputPipeline.bus.connect("message::eos", self.on_eos)
60 self.outputPipeline.bus.connect("message::error", self.on_error)
84 def is_input(self):
85 return False
6186
62 self.log.debug('Launching Output-Pipeline')
63 self.outputPipeline.set_state(Gst.State.PLAYING)
87 def __str__(self):
88 return 'AVRawOutput[{}]'.format(self.source)
89
90 def attach(self, pipeline):
91 self.pipeline = pipeline
6492
6593 def on_accepted(self, conn, addr):
6694 self.log.debug('Adding fd %u to multifdsink', conn.fileno())
67 fdsink = self.outputPipeline.get_by_name('fd')
95
96 # find fdsink and emit 'add'
97 fdsink = self.pipeline.get_by_name("fd-{}".format(self.source))
6898 fdsink.emit('add', conn.fileno())
6999
70 def on_disconnect(multifdsink, fileno):
100 # catch disconnect
101 def on_client_fd_removed(multifdsink, fileno):
71102 if fileno == conn.fileno():
72103 self.log.debug('fd %u removed from multifdsink', fileno)
73104 self.close_connection(conn)
105 fdsink.connect('client-fd-removed', on_client_fd_removed)
74106
75 def on_about_to_disconnect(multifdsink, fileno, status):
107 # catch client-removed
108 def on_client_removed(multifdsink, fileno, status):
76109 # GST_CLIENT_STATUS_SLOW = 3,
77110 if fileno == conn.fileno() and status == 3:
78111 self.log.warning('about to remove fd %u from multifdsink '
79112 'because it is too slow!', fileno)
80
81 fdsink.connect('client-fd-removed', on_disconnect)
82 fdsink.connect('client-removed', on_about_to_disconnect)
83
84 def on_eos(self, bus, message):
85 self.log.debug('Received End-of-Stream-Signal on Output-Pipeline')
86
87 def on_error(self, bus, message):
88 self.log.debug('Received Error-Signal on Output-Pipeline')
89 (error, debug) = message.parse_error()
90 self.log.debug('Error-Details: #%u: %s', error.code, debug)
113 fdsink.connect('client-removed', on_client_removed)
0 #!/usr/bin/env python3
1 import logging
2
3 from gi.repository import Gst
4
5 from lib.config import Config
6 from lib.clock import Clock
7 from lib.args import Args
8
9
10 class Blinder(object):
11 # create logging interface
12 log = logging.getLogger('Blinder')
13
14 def __init__(self):
15
16 # remember some things
17 self.acaps = Config.getAudioCaps()
18 self.vcaps = Config.getVideoCaps()
19 self.volume = Config.getBlinderVolume()
20 self.blindersources = Config.getBlinderSources()
21
22 self.log.info('Configuring video blinders for %u sources',
23 len(self.blindersources))
24
25 # open bin
26 self.bin = "" if Args.no_bins else """
27 bin.(
28 name=blinders
29 """
30
31 # list blinders
32 self.livesources = Config.getLiveSources()
33
34 # add blinder pipelines
35 for livesource in self.livesources:
36 self.bin += """
37 compositor
38 name=compositor-blinder-{livesource}
39 ! queue
40 max-size-time=3000000000
41 name=queue-video-{livesource}-blinded
42 ! tee
43 name=video-{livesource}-blinded
44
45 video-{livesource}.
46 ! queue
47 max-size-time=3000000000
48 name=queue-video-{livesource}-compositor-blinder-{livesource}
49 ! compositor-blinder-{livesource}.
50 """.format(livesource=livesource)
51
52 for blindersource in self.blindersources:
53 self.bin += """
54 video-{blindersource}.
55 ! queue
56 max-size-time=3000000000
57 name=queue-video-blinder-{blindersource}-compositor-blinder-{livesource}
58 ! compositor-blinder-{livesource}.
59 """.format(
60 blindersource=blindersource,
61 livesource=livesource
62 )
63
64 # Audiomixer
65 self.bin += """
66 audiomixer
67 name=audiomixer-blinder
68 ! audioamplify
69 amplification={volume}
70 ! queue
71 name=queue-audio-mix-blinded
72 max-size-time=3000000000
73 ! tee
74 name=audio-mix-blinded
75
76 audio-mix.
77 ! queue
78 max-size-time=3000000000
79 name=queue-capssetter-blinder
80 ! capssetter
81 caps={acaps}
82 ! queue
83 max-size-time=3000000000
84 name=queue-audiomixer-blinder
85 ! audiomixer-blinder.
86 """.format(acaps=self.acaps,
87 volume=Config.getBlinderVolume()
88 )
89
90 # Source from the Blank-Audio-Tee into the Audiomixer
91 self.bin += """
92 audio-blinder.
93 ! queue
94 max-size-time=3000000000
95 name=queue-audio-blinded-audiomixer-blinder
96 ! audiomixer-blinder.
97 """
98
99 # close bin
100 self.bin += "" if Args.no_bins else "\n)\n"
101
102 self.blind_source = 0 if len(self.blindersources) > 0 else None
103
104 def __str__(self):
105 return 'Blinder'
106
107 def attach(self, pipeline):
108 self.pipeline = pipeline
109 self.applyMixerState()
110
111 def applyMixerState(self):
112 for livesource in self.livesources:
113 self.applyMixerStateVideo(
114 'compositor-blinder-{}'.format(livesource))
115 self.applyMixerStateVideo(
116 'compositor-blinder-{}'.format(livesource))
117 self.applyMixerStateAudio('audiomixer-blinder')
118
119 def applyMixerStateVideo(self, mixername):
120 mixer = self.pipeline.get_by_name(mixername)
121 if not mixer:
122 self.log.error("Video mixer '%s' not found", mixername)
123 else:
124 mixer.get_static_pad('sink_0').set_property(
125 'alpha', int(self.blind_source is None))
126 for idx, name in enumerate(self.blindersources):
127 blinder_pad = mixer.get_static_pad('sink_%u' % (idx + 1))
128 blinder_pad.set_property(
129 'alpha', int(self.blind_source == idx))
130
131 def applyMixerStateAudio(self, mixername):
132 mixer = self.pipeline.get_by_name(mixername)
133 if not mixer:
134 self.log.error("Audio mixer '%s' not found", mixername)
135 else:
136 mixer.get_static_pad('sink_0').set_property(
137 'volume', 1.0 if self.blind_source is None else 0.0)
138 mixer.get_static_pad('sink_1').set_property(
139 'volume', 0.0 if self.blind_source is None else 1.0)
140
141 def setBlindSource(self, source):
142 self.blind_source = source
143 self.applyMixerState()
88
99 log.debug("Obtaining System-Clock")
1010 Clock = Gst.SystemClock.obtain()
11 log.info("Using System-Clock for all Pipelines: %s", Clock)
11 log.info("Using System-Clock for all pipelines.")
1212
1313 log.info("Starting NetTimeProvider on Port %u", port)
1414 NetTimeProvider = GstNet.NetTimeProvider.new(Clock, '::', port)
0 #!/usr/bin/env python3
01 import logging
12 import json
23 import inspect
34
45 from lib.config import Config
5 from lib.videomix import CompositeModes
66 from lib.response import NotifyResponse, OkResponse
77 from lib.sources import restart_source
8
8 from vocto.composite_commands import CompositeCommand
9 from vocto.command_helpers import quote, dequote, str2bool
10 import os
911
1012 class ControlServerCommands(object):
13
1114 def __init__(self, pipeline):
1215 self.log = logging.getLogger('ControlServerCommands')
1316
1417 self.pipeline = pipeline
1518 self.stored_values = {}
1619
17 self.sources = Config.getlist('mix', 'sources')
18 if Config.getboolean('stream-blanker', 'enabled'):
19 self.blankerSources = Config.getlist('stream-blanker', 'sources')
20 self.sources = Config.getSources()
21 self.blinder_sources = Config.getBlinderSources()
22 self.streams = Config.getAudioStreams().get_stream_names()
2023
2124 # Commands are defined below. Errors are sent to the clients by throwing
2225 # exceptions, they will be turned into messages outside.
8184 for source in self.sources:
8285 helplines.append("\t" + source)
8386
84 if Config.getboolean('stream-blanker', 'enabled'):
87 if Config.getBlinderEnabled():
8588 helplines.append("\n")
8689 helplines.append("Stream-Blanker Sources-Names:")
87 for source in self.blankerSources:
90 for source in self.blinder_sources:
8891 helplines.append("\t" + source)
8992
90 helplines.append("\n")
91 helplines.append("Composition-Modes:")
92 for mode in CompositeModes:
93 helplines.append("\t" + mode.name)
94
9593 return OkResponse("\n".join(helplines))
9694
9795 def _get_video_status(self):
98 a = self.sources[self.pipeline.vmix.getVideoSourceA()]
99 b = self.sources[self.pipeline.vmix.getVideoSourceB()]
96 a = self.pipeline.vmix.getVideoSourceA()
97 b = self.pipeline.vmix.getVideoSourceB()
10098 return [a, b]
10199
102100 def get_video(self):
103101 """gets the current video-status, consisting of the name of
104102 video-source A and video-source B"""
105 status = self._get_video_status()
103 status = self.pipeline.vmix.getVideoSources()
106104 return OkResponse('video_status', *status)
107105
108106 def set_video_a(self, src_name):
109107 """sets the video-source A to the supplied source-name or source-id,
110108 swapping A and B if the supplied source is currently used as
111109 video-source B"""
112 src_id = self.sources.index(src_name)
113 self.pipeline.vmix.setVideoSourceA(src_id)
114
115 status = self._get_video_status()
110 self.pipeline.vmix.setVideoSourceA(src_name)
111
112 status = self.pipeline.vmix.getVideoSources()
116113 return NotifyResponse('video_status', *status)
117114
118115 def set_video_b(self, src_name):
119116 """sets the video-source B to the supplied source-name or source-id,
120117 swapping A and B if the supplied source is currently used as
121118 video-source A"""
122 src_id = self.sources.index(src_name)
123 self.pipeline.vmix.setVideoSourceB(src_id)
124
125 status = self._get_video_status()
119 self.pipeline.vmix.setVideoSourceB(src_name)
120
121 status = self.pipeline.vmix.getVideoSources()
126122 return NotifyResponse('video_status', *status)
127123
128124 def _get_audio_status(self):
129125 volumes = self.pipeline.amix.getAudioVolumes()
130126
131127 return json.dumps({
132 self.sources[idx]: round(volume, 4)
128 self.streams[idx]: round(volume, 4)
133129 for idx, volume in enumerate(volumes)
134130 })
135131
148144
149145 def set_audio_volume(self, src_name, volume):
150146 """sets the volume of the supplied source-name or source-id"""
151 src_id = self.sources.index(src_name)
152147 volume = float(volume)
153148 if volume < 0.0:
154149 raise ValueError("volume must be positive")
155 self.pipeline.amix.setAudioSourceVolume(src_id, volume)
150 if src_name == 'mix':
151 self.pipeline.amix.setAudioVolume(volume)
152 else:
153 self.pipeline.amix.setAudioSourceVolume(self.streams.index(src_name), volume)
156154
157155 status = self._get_audio_status()
158156 return NotifyResponse('audio_status', status)
159157
160 def _get_composite_status(self):
161 mode = self.pipeline.vmix.getCompositeMode()
162 return mode.name
163
164158 def get_composite_mode(self):
165159 """gets the name of the current composite-mode"""
166 status = self._get_composite_status()
160 status = self.pipeline.vmix.getCompositeMode()
167161 return OkResponse('composite_mode', status)
168162
169163 def get_composite_modes(self):
170164 """lists the names of all available composite-mode"""
171 names = [mode.name for mode in CompositeModes]
165 # TODO: fix this...
166 #names = [mode.name for mode in CompositeModes]
167 names = [""]
172168 namestr = ','.join(names)
173169 return OkResponse('composite_modes', namestr)
174170
175171 def get_composite_mode_and_video_status(self):
176172 """retrieves the composite-mode and the video-status
177173 in a single call"""
178 composite_status = self._get_composite_status()
179 video_status = self._get_video_status()
174 composite_status = self.pipeline.vmix.getCompositeMode()
175 video_status = self.pipeline.vmix.getVideoSources()
180176 return OkResponse('composite_mode_and_video_status',
181177 composite_status, *video_status)
182178
183179 def set_composite_mode(self, mode_name):
184180 """sets the name of the id of the composite-mode"""
185 mode = CompositeModes[mode_name]
186 self.pipeline.vmix.setCompositeMode(mode)
187
188 composite_status = self._get_composite_status()
189 video_status = self._get_video_status()
181 self.pipeline.vmix.setComposite(CompositeCommand(mode_name, "*", "*"))
182
183 composite_status = self.pipeline.vmix.getCompositeMode()
184 video_status = self.pipeline.vmix.getVideoSources()
190185 return [
191186 NotifyResponse('composite_mode', composite_status),
192187 NotifyResponse('video_status', *video_status),
194189 composite_status, *video_status),
195190 ]
196191
192 def transition(self, command):
193 """sets the composite and sources by using the composite command format
194 (e.g. 'sbs(cam1,cam2)') as the only parameter
195 """
196 self.pipeline.vmix.setComposite(command, True)
197 return NotifyResponse('composite', self.pipeline.vmix.getComposite())
198
199 def best(self, command):
200 """tests if transition to the composite described by command is possible.
201 """
202 transition = self.pipeline.vmix.testTransition(command)
203 if transition:
204 return OkResponse('best','transition', *transition)
205 else:
206 cut = self.pipeline.vmix.testCut(command)
207 if cut:
208 return OkResponse('best','cut', *cut)
209 else:
210 command = CompositeCommand.from_str(command)
211 return OkResponse('best','none',command.A,command.B)
212
213 def cut(self, command):
214 """sets the composite and sources by using the composite command format
215 (e.g. 'sbs(cam1,cam2)') as the only parameter
216 """
217 self.pipeline.vmix.setComposite(command, False)
218 return NotifyResponse('composite', self.pipeline.vmix.getComposite())
219
220 def get_composite(self):
221 """fetch current composite and sources using the composite command format
222 (e.g. 'sbs(cam1,cam2)') as return value
223 """
224 return OkResponse('composite', self.pipeline.vmix.getComposite())
225
197226 def set_videos_and_composite(self, src_a_name, src_b_name,
198227 mode_name):
199228 """sets the A- and the B-source synchronously with the composition-mode
200229 all parametets can be set to "*" which will leave them unchanged."""
201 if src_a_name != '*':
202 src_a_id = self.sources.index(src_a_name)
203 self.pipeline.vmix.setVideoSourceA(src_a_id)
204
205 if src_b_name != '*':
206 src_b_id = self.sources.index(src_b_name)
207 self.pipeline.vmix.setVideoSourceB(src_b_id)
208
209 if mode_name != '*':
210 mode = CompositeModes[mode_name]
211 called_with_source = \
212 src_a_name != '*' or \
213 src_b_name != '*'
214
215 self.pipeline.vmix.setCompositeMode(
216 mode, apply_default_source=not called_with_source)
217
218 composite_status = self._get_composite_status()
219 video_status = self._get_video_status()
230 self.pipeline.vmix.setComposite(
231 str(CompositeCommand(mode_name, src_a_name, src_b_name)))
232
233 composite_status = self.pipeline.vmix.getCompositeMode()
234 video_status = self.pipeline.vmix.getVideoSources()
220235
221236 return [
222237 NotifyResponse('composite_mode', composite_status),
225240 composite_status, *video_status),
226241 ]
227242
228 if Config.getboolean('stream-blanker', 'enabled'):
243 if Config.getBlinderEnabled():
229244 def _get_stream_status(self):
230 blankSource = self.pipeline.streamblanker.blankSource
231 if blankSource is None:
245 blind_source = self.pipeline.blinder.blind_source
246 if blind_source is None:
232247 return ('live',)
233248
234 return 'blank', self.blankerSources[blankSource]
249 return 'blinded', self.blinder_sources[blind_source]
235250
236251 def get_stream_status(self):
237 """gets the current streamblanker-status"""
252 """gets the current blinder-status"""
238253 status = self._get_stream_status()
239254 return OkResponse('stream_status', *status)
240255
241 def set_stream_blank(self, source_name):
242 """sets the streamblanker-status to blank with the specified
243 blanker-source-name or -id"""
244 src_id = self.blankerSources.index(source_name)
245 self.pipeline.streamblanker.setBlankSource(src_id)
256 def set_stream_blind(self, source_name):
257 """sets the blinder-status to blinder with the specified
258 blinder-source-name or -id"""
259 src_id = self.blinder_sources.index(source_name)
260 self.pipeline.blinder.setBlindSource(src_id)
246261
247262 status = self._get_stream_status()
248263 return NotifyResponse('stream_status', *status)
249264
265 # for backwards compatibility this command remains obsolete
266 def set_stream_blank(self, source_name):
267
268 return self.set_stream_blind(source_name)
269
250270 def set_stream_live(self):
251 """sets the streamblanker-status to live"""
252 self.pipeline.streamblanker.setBlankSource(None)
271 """sets the blinder-status to live"""
272 self.pipeline.blinder.setBlindSource(None)
253273
254274 status = self._get_stream_status()
255275 return NotifyResponse('stream_status', *status)
269289 """restarts the specified source"""
270290 restart_source(src_name)
271291 return OkResponse('source_restarted', src_name)
292
293 def report_queues(self):
294 report = dict()
295 for queue in self.pipeline.queues:
296 report[queue.name] = queue.get_property("current-level-time")
297 return OkResponse('queue_report', json.dumps(report))
298
299 def report_ports(self):
300 for p in self.pipeline.ports:
301 p.update()
302 return OkResponse('port_report', json.dumps(self.pipeline.ports, default=lambda x: x.todict()))
303
304 # only available when overlays are configured
305 if Config.hasOverlay():
306
307 def set_overlay(self, overlay):
308 """set an overlay and show"""
309 # decode parameter to filename
310 filename = Config.getOverlayFilePath(dequote(overlay))
311 # check if file exists
312 if os.path.isfile(filename):
313 # select overlay in mixing pipeline
314 self.pipeline.vmix.setOverlay(filename)
315 else:
316 # tell log about file that could not be found
317 self.log.error(
318 "Overlay file '{}' not found".format(filename))
319 # respond with current overlay notification
320 return self.get_overlay()
321
322 def show_overlay(self, visible):
323 """set an overlay and show"""
324 # show or hide overlay in mixing pipeline
325 self.pipeline.vmix.showOverlay(str2bool(visible))
326 # respond with overlay visibility notification
327 return self.get_overlay_visible()
328
329 def get_overlay(self):
330 """respond any visible overlay"""
331 return NotifyResponse('overlay', quote(Config.getOverlayNameFromFilePath(self.pipeline.vmix.getOverlay())))
332
333 def get_overlay_visible(self):
334 """respond any visible overlay"""
335 return NotifyResponse('overlay_visible', str(self.pipeline.vmix.getOverlayVisible()))
336
337 def get_overlays_title(self):
338 """respond with list of all available overlays"""
339 return NotifyResponse('overlays_title',
340 ",".join(quote(t) for t in Config.getOverlaysTitle()))
341
342 def get_overlays(self):
343 return NotifyResponse('overlays',
344 ",".join([quote(a) for a in Config.getOverlayFiles()]))
0 #!/usr/bin/env python3
01 import os.path
12 import logging
2 from configparser import SafeConfigParser, DuplicateSectionError
3 from configparser import DuplicateSectionError
34 from lib.args import Args
5 from lib.sources import kind_has_audio
6 from vocto.config import VocConfigParser
7 import xml.etree.ElementTree as ET
8 from datetime import date, datetime, timedelta
9 import re
10 import os
411
512 __all__ = ['Config']
613
714 Config = None
815
916
10 class VocConfigParser(SafeConfigParser):
11 def getlist(self, section, option):
12 option = self.get(section, option).strip()
13 if len(option) == 0:
14 return []
15
16 unfiltered = [x.strip() for x in option.split(',')]
17 return list(filter(None, unfiltered))
17 def scandatetime(str):
18 return datetime.strptime(str[:19], "%Y-%m-%dT%H:%M:%S")
19
20
21 def scanduration(str):
22 r = re.match(r'^(\d+):(\d+)$', str)
23 return timedelta(hours=int(r.group(1)), minutes=int(r.group(2)))
24
25
26 class VoctocoreConfigParser(VocConfigParser):
27
28 def __init__(self):
29 super().__init__()
30 self.events = []
31 self.event_now = None
32 self.events_update = None
33 self.default_insert = None
1834
1935 def add_section_if_missing(self, section):
2036 try:
2238 except DuplicateSectionError:
2339 pass
2440
41 def getOverlayFile(self):
42 ''' return overlay/file or <None> from INI configuration '''
43 if self.has_option('overlay', 'file'):
44 return self.getOverlayNameFromFilePath(self.get('overlay', 'file'))
45 else:
46 return None
47
48 def getScheduleRoom(self):
49 ''' return overlay/room or <None> from INI configuration '''
50 if self.has_option('overlay', 'room'):
51 return self.get('overlay', 'room')
52 else:
53 return None
54
55 def getScheduleEvent(self):
56 ''' return overlay/event or <None> from INI configuration '''
57 if self.has_option('overlay', 'event'):
58 if self.has_option('overlay', 'room'):
59 self.log.warning(
60 "'overlay'/'event' overwrites 'overlay'/'room'")
61 return self.get('overlay', 'event')
62 else:
63 return None
64
65 def getSchedule(self):
66 ''' return overlay/schedule or <None> from INI configuration '''
67 if self.has_option('overlay', 'schedule'):
68 if self.has_option('overlay', 'room') or self.has_option('overlay', 'event'):
69 return self.get('overlay', 'schedule')
70 else:
71 # warn if no room has been defined
72 self.log.error(
73 "configuration option 'overlay'/'schedule' ignored when not defining 'room' or 'event' too")
74 return None
75
76 def _getEvents(self):
77 # check if file has been changed before re-read
78 self.events = None
79 # parse XML and iterate all schedule/day elements
80 self.events = ET.parse(
81 self.getSchedule()).getroot() .findall('day/room/event')
82 self.log.info("read {n} events from file \'{f}\'".format(
83 n=len(self.events), f=self.getSchedule()))
84 # remember the update time
85 self.events_update = os.path.getmtime(self.getSchedule())
86 return self.events
87
88 def _getEventNow(self):
89 # get currnet date and time
90 now = datetime.now()
91 # check for option overlay/event
92 if self.getScheduleEvent():
93 # find event by ID
94 for event in self._getEvents():
95 if event.get('id') == self.getScheduleEvent():
96 # remember current event
97 self.event_now = event
98 else:
99 # check if there is no event already running
100 if (not self.event_now) or now > scandatetime(self.event_now.find('date').text) + scanduration(self.event_now.find('duration').text):
101 # inistialize a past date
102 past = datetime(1999, 1, 1)
103 # remember nearest start time
104 nowest = past
105 # iterate events
106 for event in self._getEvents():
107 # check for room
108 if event.find('room').text == self.getScheduleRoom() or not self.getScheduleRoom():
109 # get start time
110 time = scandatetime(event.find('date').text)
111 # time nearer then nowest
112 if now >= time and time > nowest:
113 # remember new nearest time
114 nowest = time
115 # rememeber current event
116 self.event_now = event
117 return self.event_now
118
119 def getOverlaysPath(self):
120 ''' return overlays path or $PWD from INI configuration '''
121 if self.has_option('overlay', 'path'):
122 return os.path.abspath(self.get('overlay', 'path'))
123 else:
124 return os.getcwd()
125
126 def getOverlaysTitle(self):
127 if self.getSchedule():
128 try:
129 event = self._getEventNow()
130 if event:
131 at = scandatetime(event.find('date').text)
132 return (at.strftime("%Y-%m-%d %H:%M"),
133 (at + scanduration(event.find('duration').text)
134 ).strftime("%Y-%m-%d %H:%M"),
135 event.get('id'),
136 event.find('title').text)
137 except FileNotFoundError:
138 self.log.error(
139 'schedule file \'%s\' not found', self.getSchedule())
140 return None
141
142 def getOverlayFilePath(self, overlay):
143 ''' return absolute file path to overlay by given string of overlay
144 file name (and maybe |-separated name)
145 '''
146 # return None if None was given
147 if not overlay:
148 return None
149 # split overlay by "|" if applicable
150 filename, name = overlay.split(
151 "|") if "|" in overlay else (overlay, None)
152 # add PNG default extension if not already within filename
153 filename = filename + \
154 ".png" if filename and (
155 len(filename) < 4 or filename[-4:].lower()) != ".png" else filename
156 # return absolute path of filename
157 return os.path.join(self.getOverlaysPath(), filename)
158
159 def getOverlayNameFromFilePath(self, filepath):
160 ''' return overlay name from filepath (which may have |-separated
161 name attached)
162 '''
163 # return None if None was given
164 if not filepath:
165 return None
166 # split filepath by "|" if applicable
167 filepath, name = filepath.split(
168 "|") if '|' in filepath else (filepath, None)
169 # remove overlay path
170 filename = filepath.replace(self.getOverlaysPath() + os.sep, "")
171 # remove PNG extension
172 filename = filename[:-4] if filename and len(
173 filename) > 4 and filename[-4:].lower() == ".png" else filename
174 # attach name again if it was given
175 return "|".join((filename, name)) if name else filename
176
177 def getOverlayFiles(self):
178 ''' generate list of available overlay files by the following opportunities:
179 - by 'schedule' (and optionally 'room') from section 'overlay'
180 - by 'files' from section 'overlay'
181 - by 'file' from section 'overlay'
182 '''
183 # initialize empty inserts list
184 inserts = []
185
186 # checkt for overlay/schedule option
187 if self.getSchedule():
188 try:
189
190 def generate(event):
191 ''' return all available insert names for event '''
192 # get list of persons from event
193 persons = event.findall("persons/person")
194 # generate insert file names and names
195 inserts = ["event_{eid}_person_{pid}|{text}".format(
196 eid=event.get('id'),
197 pid=person.get('id'),
198 text=person.text) for person in persons]
199 # add a insert for all persons together
200 if len(persons) > 1:
201 inserts += ["event_{eid}_persons|{text}".format(
202 eid=event.get('id'),
203 text=", ".join([person.text for person in persons]))]
204 return inserts
205
206 # get current event
207 event = self._getEventNow()
208 # generate inserts from event
209 inserts = generate(event)
210 # if empty show warning
211 if not inserts:
212 self.log.warning('schedule file \'%s\' contains no information for inserts of event #%s',
213 self.getSchedule(),
214 event.get('id'))
215 except FileNotFoundError:
216 # show error at file not found
217 self.log.error('schedule file \'%s\' not found',
218 self.getSchedule())
219 # check for overlay/files option
220 if self.has_option('overlay', 'files'):
221 # add inserts from files
222 inserts += [self.getOverlayNameFromFilePath(o)
223 for o in self.getList('overlay', 'files')]
224 # check overlay/file option
225 if self.getOverlayFile():
226 # append this file if not already in list
227 if not self.getOverlayNameFromFilePath(self.getOverlayFile()) in inserts:
228 inserts += [self.getOverlayNameFromFilePath(self.getOverlayFile())]
229 # make a list of inserts with existing image files
230 valid = []
231 for i in inserts:
232 # get absolute file path
233 filename = self.getOverlayFilePath(i.split('|')[0])
234 if os.path.isfile(filename):
235 # append to valid if existing
236 valid.append(i)
237 else:
238 # report error if not found
239 self.log.error(
240 'Could not find overlay image file \'%s\'.' % filename )
241 # check if there is any useful result
242 if valid:
243 self.default_insert = valid[0]
244 self.log.info('found %d insert(s): %s',
245 len(valid),
246 ",".join([i for i in valid]))
247 else:
248 self.default_insert = None
249 self.log.warning(
250 'Could not find any availbale overlays in configuration.')
251 return valid
252
253 def getOverlayBlendTime(self):
254 ''' return overlay blending time in milliseconds from INI configuration '''
255 if self.has_option('overlay', 'blend'):
256 return int(self.get('overlay', 'blend'))
257 else:
258 return 300
259
260 def getAudioSources(self, internal=False):
261 def source_has_audio(source):
262 return kind_has_audio(self.getSourceKind(source))
263 sources = self.getSources()
264 if internal:
265 sources += ['mix']
266 if self.getBlinderEnabled():
267 sources += ['blinder', 'mix-blinded']
268 return list(filter(source_has_audio, sources))
269
25270
26271 def load():
27272 global Config
28 files = [
29 os.path.join(os.path.dirname(os.path.realpath(__file__)),
30 '../default-config.ini'),
31 os.path.join(os.path.dirname(os.path.realpath(__file__)),
32 '../config.ini'),
33 '/etc/voctomix/voctocore.ini',
34 '/etc/voctomix.ini', # deprecated
35 '/etc/voctocore.ini',
36 os.path.expanduser('~/.voctomix.ini'), # deprecated
37 os.path.expanduser('~/.voctocore.ini'),
38 ]
39
40 if Args.ini_file is not None:
41 files.append(Args.ini_file)
42
43 Config = VocConfigParser()
44 readfiles = Config.read(files)
273
274 Config = VoctocoreConfigParser()
275
276 config_file_name = Args.ini_file if Args.ini_file else os.path.join(
277 os.path.dirname(os.path.realpath(__file__)), '../default-config.ini')
278 readfiles = Config.read([config_file_name])
45279
46280 log = logging.getLogger('ConfigParser')
47 log.debug('considered config-files: \n%s',
48 "\n".join([
49 "\t\t" + os.path.normpath(file)
50 for file in files
51 ]))
52 log.debug('successfully parsed config-files: \n%s',
53 "\n".join([
54 "\t\t" + os.path.normpath(file)
55 for file in readfiles
56 ]))
281 log.debug("successfully parsed config-file: '%s'", config_file_name)
57282
58283 if Args.ini_file is not None and Args.ini_file not in readfiles:
59284 raise RuntimeError('explicitly requested config-file "{}" '
55 from lib.tcpmulticonnection import TCPMultiConnection
66 from lib.response import NotifyResponse
77
8 from vocto.port import Port
9
810
911 class ControlServer(TCPMultiConnection):
1012
1113 def __init__(self, pipeline):
1214 '''Initialize server and start listening.'''
1315 self.log = logging.getLogger('ControlServer')
14 super().__init__(port=9999)
16 super().__init__(port=Port.CORE_LISTENING)
1517
1618 self.command_queue = Queue()
1719
9092 'stopping on_loop scheduling')
9193 return True
9294
95 self.log.info("processing command '%s'", ' '.join(words))
96
9397 command = words[0]
9498 args = words[1:]
95
96 self.log.info("processing command %r with args %s", command, args)
9799
98100 response = None
99101 try:
100102 # deny calling private methods
101103 if command[0] == '_':
102 self.log.info('private methods are not callable')
104 self.log.info('Private methods are not callable')
103105 raise KeyError()
104106
105107 command_function = self.commands.__class__.__dict__[command]
106108
107109 except KeyError as e:
108 self.log.info("received unknown command %s", command)
110 self.log.info("Received unknown command %s", command)
109111 response = "error unknown command %s\n" % command
110112
111113 else:
157159 return False
158160
159161 message = queue.get()
162 self.log.info("Responding message '%s'", message.strip())
160163 try:
161164 conn.send(message.encode())
162165 except Exception as e:
163 self.log.warning('failed to send message', exc_info=True)
166 self.log.warning("Failed to send message '%s'", message.encode(), exc_info=True)
164167
165168 return True
1414 c_mod = 32
1515 c_msg = 0
1616
17 if record.levelno == logging.WARNING:
17 if record.levelno <= logging.DEBUG:
18 c_msg = 90
19
20 elif record.levelno <= logging.INFO:
21 c_lvl = 37
22 c_msg = 97
23
24 elif record.levelno <= logging.WARNING:
1825 c_lvl = 31
19 # c_mod = 33
2026 c_msg = 33
2127
22 elif record.levelno > logging.WARNING:
28 else:
2329 c_lvl = 31
2430 c_mod = 31
2531 c_msg = 31
0 #!/usr/bin/env python3
1 from gi.repository import Gst, GstController
2 import logging
3 import gi
4 gi.require_version('GstController', '1.0')
5
6
7 class Overlay:
8 log = logging.getLogger('Overlay')
9
10 def __init__(self, pipeline, location=None, blend_time=300):
11 # get overlay element and config
12 self.overlay = pipeline.get_by_name('overlay')
13 self.location = location
14 self.isVisible = location != None
15 self.blend_time = blend_time
16
17 # initialize overlay control binding
18 self.alpha = GstController.InterpolationControlSource()
19 self.alpha.set_property('mode', GstController.InterpolationMode.LINEAR)
20 cb = GstController.DirectControlBinding.new_absolute(self.overlay, 'alpha', self.alpha)
21 self.overlay.add_control_binding(cb)
22
23 def set( self, location ):
24 self.location = location if location else ""
25 if self.isVisible:
26 self.overlay.set_property('location', self.location )
27
28 def show(self, visible, playtime):
29 ''' set overlay visibility '''
30 # check if control binding is available
31 assert self.alpha
32 # if state changes
33 if self.isVisible != visible:
34 # set blending animation
35 if self.blend_time > 0:
36 self.alpha.set(playtime, 0.0 if visible else 1.0)
37 self.alpha.set(playtime + int(Gst.SECOND / 1000.0 * self.blend_time), 1.0 if visible else 0.0)
38 else:
39 self.alpha.set(playtime, 1.0 if visible else 0.0)
40 # set new visibility state
41 self.isVisible = visible
42 # re-set location if we get visible
43 if visible:
44 self.overlay.set_property('location', self.location )
45
46 def get(self):
47 ''' get current overlay file location '''
48 return self.location
49
50 def visible(self):
51 ''' get overlay visibility '''
52 return self.isVisible
0 #!/usr/bin/env python3
01 import logging
2 import re
3 import sys
4
5 from gi.repository import Gst
16
27 # import library components
38 from lib.config import Config
611 from lib.avpreviewoutput import AVPreviewOutput
712 from lib.videomix import VideoMix
813 from lib.audiomix import AudioMix
9 from lib.streamblanker import StreamBlanker
10
14 from lib.blinder import Blinder
15 from lib.args import Args
16 from lib.clock import Clock
17 from vocto.port import Port
18 from vocto.debug import gst_generate_dot
19 from vocto.pretty import pretty
1120
1221 class Pipeline(object):
1322 """mixing, streaming and encoding pipeline constuction and control"""
1423
1524 def __init__(self):
1625 self.log = logging.getLogger('Pipeline')
17 self.log.info('Video-Caps configured to: %s',
18 Config.get('mix', 'videocaps'))
19 self.log.info('Audio-Caps configured to: %s',
20 Config.get('mix', 'audiocaps'))
26 # log capabilities
27 self.log.info('Video-Caps configured to: %s', Config.getVideoCaps())
28 self.log.info('Audio-Caps configured to: %s', Config.getAudioCaps())
2129
22 names = Config.getlist('mix', 'sources')
23 if len(names) < 1:
30 # get A/B sources from config
31 sources = Config.getSources()
32 if len(sources) < 1:
2433 raise RuntimeError("At least one AVSource must be configured!")
2534
26 self.sources = []
27 self.mirrors = []
28 self.previews = []
29 self.sbsources = []
35 # collect bins for all modules
36 self.bins = []
37 self.ports = []
3038
31 self.log.info('Creating %u AVSources: %s', len(names), names)
32 for idx, name in enumerate(names):
33 port = 10000 + idx
39 # create A/V sources
40 self.log.info('Creating %u AVSources: %s', len(sources), sources)
41 for idx, source_name in enumerate(sources):
42 # count port and create source
43 source = spawn_source(source_name, Port.SOURCES_IN + idx)
44 self.bins.append(source)
45 self.ports.append(Port(source_name, source))
3446
35 outputs = [name + '_mixer']
36 if Config.getboolean('previews', 'enabled'):
37 outputs.append(name + '_preview')
47 if Config.getMirrorsEnabled():
48 if source_name in Config.getMirrorsSources():
49 dest = AVRawOutput(source_name, Port.SOURCES_OUT + idx)
50 self.bins.append(dest)
51 self.ports.append(Port(source_name, dest))
3852
39 if Config.getboolean('mirrors', 'enabled'):
40 outputs.append(name + '_mirror')
53 # check for source preview selection
54 if Config.getPreviewsEnabled():
55 # count preview port and create source
56 dest = AVPreviewOutput(source_name, Port.SOURCES_PREVIEW + idx)
57 self.bins.append(dest)
58 self.ports.append(Port("preview-%s" % source_name, dest))
4159
42 if Config.has_option('mix', 'slides_source_name') and \
43 Config.get('mix', 'slides_source_name') == name:
44 outputs.append('slides_stream-blanker')
45
46 source = spawn_source(name, port, outputs=outputs)
47 self.log.info('Creating AVSource %s as %s', name, source)
48 self.sources.append(source)
49
50 if Config.getboolean('mirrors', 'enabled'):
51 port = 13000 + idx
52 self.log.info('Creating Mirror-Output for AVSource %s '
53 'at tcp-port %u', name, port)
54
55 mirror = AVRawOutput('%s_mirror' % name, port)
56 self.mirrors.append(mirror)
57
58 if Config.getboolean('previews', 'enabled'):
59 port = 14000 + idx
60 self.log.info('Creating Preview-Output for AVSource %s '
61 'at tcp-port %u', name, port)
62
63 preview = AVPreviewOutput('%s_preview' % name, port)
64 self.previews.append(preview)
65
66 self.log.info('Creating Videmixer')
67 self.vmix = VideoMix()
68
60 # create audio mixer
6961 self.log.info('Creating Audiomixer')
7062 self.amix = AudioMix()
63 self.bins.append(self.amix)
7164
72 port = 16000
73 self.bgsrc = spawn_source('background', port, has_audio=False)
74 self.log.info('Creating Mixer-Background VSource as %s', self.bgsrc)
65 # create video mixer
66 self.log.info('Creating Videomixer')
67 self.vmix = VideoMix()
68 self.bins.append(self.vmix)
7569
76 port = 11000
77 self.log.info('Creating Mixer-Output at tcp-port %u', port)
78 self.mixout = AVRawOutput('mix_out', port)
70 for idx, background in enumerate(Config.getBackgroundSources()):
71 # create background source
72 source = spawn_source(
73 background, Port.SOURCES_BACKGROUND+idx, has_audio=False)
74 self.bins.append(source)
75 self.ports.append(Port(background, source))
7976
80 if Config.getboolean('previews', 'enabled'):
81 port = 12000
82 self.log.info('Creating Preview-Output for Mix'
83 'at tcp-port %u', port)
77 # create mix TCP output
78 dest = AVRawOutput('mix', Port.MIX_OUT, use_audio_mix=True)
79 self.bins.append(dest)
80 self.ports.append(Port('mix', dest))
8481
85 self.mixpreview = AVPreviewOutput('mix_preview', port)
82 # create mix preview TCP output
83 if Config.getPreviewsEnabled():
84 dest = AVPreviewOutput('mix', Port.MIX_PREVIEW, use_audio_mix=True)
85 self.bins.append(dest)
86 self.ports.append(Port('preview-mix', dest))
8687
87 if Config.getboolean('stream-blanker', 'enabled'):
88 names = Config.getlist('stream-blanker', 'sources')
89 if len(names) < 1:
90 raise RuntimeError('At least one StreamBlanker-Source must '
88 # create blinding sources and mixer
89 if Config.getBlinderEnabled():
90 sources = Config.getBlinderSources()
91 if len(sources) < 1:
92 raise RuntimeError('At least one Blinder-Source must '
9193 'be configured or the '
92 'StreamBlanker disabled!')
93 for idx, name in enumerate(names):
94 port = 17000 + idx
94 'Blinder disabled!')
95 if Config.isBlinderDefault():
96 source = spawn_source('blinder',
97 Port.SOURCES_BLANK)
98 self.bins.append(source)
99 self.ports.append(Port('blinder', source))
100 else:
101 for idx, source_name in enumerate(sources):
102 source = spawn_source(source_name,
103 Port.SOURCES_BLANK + idx,
104 has_audio=False)
105 self.bins.append(source)
106 self.ports.append(Port('blinded-{}'.format(source_name), source))
95107
96 source = spawn_source('stream-blanker-{}'.format(name), port,
97 has_audio=False)
98 self.log.info('Creating StreamBlanker VSource %s as %s',
99 name, source)
100 self.sbsources.append(source)
108 source = spawn_source('blinder',
109 Port.AUDIO_SOURCE_BLANK,
110 has_video=False)
111 self.bins.append(source)
112 self.ports.append(Port('blinder-audio', source))
101113
102 port = 18000
103 self.log.info('Creating StreamBlanker ASource at tcp-port %u',
104 port)
114 self.log.info('Creating Blinder')
115 self.blinder = Blinder()
116 self.bins.append(self.blinder)
105117
106 source = spawn_source('stream-blanker',
107 port,
108 has_video=False,
109 force_num_streams=1)
110 self.sbsources.append(source)
118 # check for source preview selection
119 if Config.getPreviewsEnabled():
120 for idx, livepreview in enumerate(Config.getLivePreviews()):
121 dest = AVPreviewOutput('{}-blinded'.format(livepreview), Port.LIVE_PREVIEW+idx, use_audio_mix=True, audio_blinded=True)
122 self.bins.append(dest)
123 self.ports.append(Port('preview-{}-live'.format(livepreview), dest))
111124
112 self.log.info('Creating StreamBlanker')
113 self.streamblanker = StreamBlanker()
125 for idx, livesource in enumerate(Config.getLiveSources()):
126 dest = AVRawOutput('{}-blinded'.format(livesource), Port.LIVE_OUT + idx, use_audio_mix=True, audio_blinded=True )
127 self.bins.append(dest)
128 self.ports.append(Port('{}-live'.format(livesource), dest))
114129
115 port = 15000
116 self.log.info('Creating StreamBlanker-Output at tcp-port %u', port)
117 self.streamout = AVRawOutput('stream-blanker_out', port)
130 for bin in self.bins:
131 self.log.info("%s\n%s", bin, pretty(bin.bin))
118132
119 if Config.has_option('mix', 'slides_source_name'):
120 port = 15001
121 self.log.info(
122 'Creating SlideStreamBlanker-Output at tcp-port %u', port)
123 self.slides_streamout = AVRawOutput(
124 'slides_stream-blanker_out', port)
133 # concatinate pipeline string
134 pipeline = "\n\n".join(bin.bin for bin in self.bins)
135
136 if Args.pipeline:
137 with open("core.pipeline.txt","w") as file:
138 file.write(pretty(pipeline))
139
140 self.prevstate = None
141 try:
142 # launch gstreamer pipeline
143 self.pipeline = Gst.parse_launch(pipeline)
144 self.log.info("pipeline launched successfuly")
145 except:
146 self.log.error("Can not launch pipeline")
147 sys.exit(-1)
148
149 # attach pads
150 for bin in self.bins:
151 bin.attach(self.pipeline)
152
153 self.pipeline.use_clock(Clock)
154
155 # fetch all queues
156 self.queues = self.fetch_elements_by_name(r'^queue-[\w_-]+$')
157
158 self.log.debug('Binding End-of-Stream-Signal on Source-Pipeline')
159 self.pipeline.bus.add_signal_watch()
160 self.pipeline.bus.connect("message::eos", self.on_eos)
161 self.pipeline.bus.connect("message::error", self.on_error)
162 self.pipeline.bus.connect(
163 "message::state-changed", self.on_state_changed)
164
165 self.pipeline.set_state(Gst.State.PLAYING)
166
167 def fetch_elements_by_name(self, regex):
168 # fetch all watchdogs
169 result = []
170
171 def query(element):
172 if re.match(regex, element.get_name()):
173 result.append(element)
174 self.pipeline.iterate_recurse().foreach(query)
175 return result
176
177 def on_eos(self, bus, message):
178 self.log.debug('Received End-of-Stream-Signal on Source-Pipeline')
179
180 def on_error(self, bus, message):
181 (error, debug) = message.parse_error()
182 self.log.error("GStreamer pipeline element '%s' signaled an error #%u: %s" % (message.src.name, error.code, error.message) )
183 sys.exit(-1)
184
185 def on_state_changed(self, bus, message):
186 newstate = message.parse_state_changed().newstate
187 states = ["PENDING", "NULL", "READY", "PAUSED", "PLAYING"]
188 self.log.debug("element state changed to '%s' by element '%s'", states[newstate], message.src.name )
189 if self.prevstate != newstate and message.src.name == "pipeline0":
190 self.prevstate = newstate
191 self.log.debug("pipeline state changed to '%s'", states[newstate] )
192 if newstate == Gst.State.PLAYING:
193 self.log.info("\n\n====================== UP AN RUNNING ======================\n" )
194
195 if Args.dot or Args.gst_debug_details:
196 # make DOT file from pipeline
197 gst_generate_dot(self.pipeline, "core.pipeline")
0 #!/usr/bin/env python3
1 import logging
2 import gi
3 gi.require_version('GstController', '1.0')
4 from gi.repository import Gst, GstController
5 from vocto.transitions import Frame, L, T, R, B
6
7 class Scene:
8 """ Scene is the adaptor between the gstreamer compositor
9 and voctomix frames.
10 With commit() you add frames at a specified play time
11 """
12 log = logging.getLogger('Scene')
13
14 def __init__(self, sources, pipeline, fps, start_sink, cropping=True):
15 """ initialize with a gstreamer pipeline and names
16 of the sources to manage
17 """
18 # frames to apply from
19 self.frames = dict()
20 # binding pads to apply to
21 self.pads = dict()
22 self.cpads = dict() if cropping else None
23 # time per frame
24 self.frame_time = int(Gst.SECOND / fps)
25
26 def bind(pad, prop):
27 """ adds a binding to a gstreamer property
28 pad's property
29 """
30 # set up a new control source
31 cs = GstController.InterpolationControlSource()
32 # stop control source's internal interpolation
33 cs.set_property(
34 'mode', GstController.InterpolationMode.NONE)
35 # create control binding
36 cb = GstController.DirectControlBinding.new_absolute(
37 pad, prop, cs)
38 # add binding to pad
39 pad.add_control_binding(cb)
40 # return binding
41 return cs
42
43 # walk all sources
44 for idx, source in enumerate(sources):
45 # initially invisible
46 self.frames[source] = None
47 # get mixer pad from pipeline
48 mixerpad = (pipeline
49 .get_by_name('videomixer')
50 .get_static_pad('sink_%s' % (idx + start_sink)))
51 # add dictionary of binds to all properties
52 # we vary for this source
53 self.pads[source] = {
54 'xpos': bind(mixerpad, 'xpos'),
55 'ypos': bind(mixerpad, 'ypos'),
56 'width': bind(mixerpad, 'width'),
57 'height': bind(mixerpad, 'height'),
58 'alpha': bind(mixerpad, 'alpha'),
59 'zorder': bind(mixerpad, 'zorder'),
60 }
61 # get mixer and cropper pad from pipeline
62 if self.cpads is not None:
63 cropperpad = (pipeline
64 .get_by_name("cropper-%s" % source))
65 self.cpads[source] = {
66 'croptop': bind(cropperpad, 'top'),
67 'cropleft': bind(cropperpad, 'left'),
68 'cropbottom': bind(cropperpad, 'bottom'),
69 'cropright': bind(cropperpad, 'right')
70 }
71 # ready to initialize gstreamer
72 self.dirty = False
73
74 def commit(self, source, frames):
75 ''' commit multiple frames to the current gstreamer scene '''
76 self.log.debug("Commit %d frame(s) to source %s", len(frames), source)
77 self.frames[source] = frames
78 self.dirty = True
79
80 def set(self, source, frame):
81 ''' commit single frame to the current gstreamer scene '''
82 self.log.debug("Set frame to source %s", source)
83 self.frames[source] = [frame]
84 self.dirty = True
85
86 def push(self, at_time=0):
87 ''' apply all committed frames to GStreamer pipeline '''
88 # get pad for given source
89 for source, frames in self.frames.items():
90 if not frames:
91 frames = [Frame(zorder=-1,alpha=0)]
92 self.log.info("Pushing %d frame(s) to source '%s' at time %dms", len(
93 frames), source, at_time / Gst.MSECOND)
94 # reset time
95 time = at_time
96 # get GStreamer property pad for this source
97 pad = self.pads[source]
98 cpad = self.cpads[source] if self.cpads else None
99 self.log.debug(" %s", Frame.str_title())
100 # apply all frames of this source to GStreamer pipeline
101 for idx, frame in enumerate(frames):
102 self.log.debug("%2d: %s", idx, frame)
103 cropped = frame.cropped()
104 alpha = frame.float_alpha()
105 # transmit frame properties into mixing pipeline
106 pad['xpos'].set(time, cropped[L])
107 pad['ypos'].set(time, cropped[T])
108 pad['width'].set(time, cropped[R] - cropped[L])
109 pad['height'].set(time, cropped[B] - cropped[T])
110 pad['alpha'].set(time, alpha)
111 pad['zorder'].set(time, frame.zorder if alpha != 0 else -1)
112 if cpad:
113 cpad['croptop'].set(time, frame.crop[T])
114 cpad['cropleft'].set(time, frame.crop[L])
115 cpad['cropbottom'].set(time, frame.crop[B])
116 cpad['cropright'].set(time, frame.crop[R])
117 # next frame time
118 time += self.frame_time
119 self.frames[source] = None
120 self.dirty = False
00 import logging
1
2 from lib.config import Config
3 from lib.sources.decklinkavsource import DeckLinkAVSource
4 from lib.sources.imgvsource import ImgVSource
5 from lib.sources.tcpavsource import TCPAVSource
61
72 log = logging.getLogger('AVSourceManager')
83
94 sources = {}
105
116
12 def spawn_source(name, port, outputs=None,
13 has_audio=True, has_video=True,
14 force_num_streams=None):
7 def spawn_source(name, port, has_audio=True, has_video=True):
158
16 section = 'source.{}'.format(name)
17 kind = Config.get(section, 'kind', fallback='tcp')
9 from lib.config import Config
10 from lib.sources.decklinkavsource import DeckLinkAVSource
11 from lib.sources.imgvsource import ImgVSource
12 from lib.sources.tcpavsource import TCPAVSource
13 from lib.sources.testsource import TestSource
14 from lib.sources.filesource import FileSource
15 from lib.sources.v4l2source import V4l2AVSource
16
17 kind = Config.getSourceKind(name)
1818
1919 if kind == 'img':
20 sources[name] = ImgVSource(name, outputs, has_audio, has_video)
21 return sources[name]
20 sources[name] = ImgVSource(name)
21 elif kind == 'decklink':
22 sources[name] = DeckLinkAVSource(name, has_audio, has_video)
23 elif kind == 'file':
24 sources[name] = FileSource(name, has_audio, has_video)
25 elif kind == 'tcp':
26 sources[name] = TCPAVSource(name, port, has_audio, has_video)
27 elif kind == 'v4l2':
28 sources[name] = V4l2AVSource(name)
29 else:
30 if kind != 'test':
31 log.warning(
32 'Unknown value "%s" in attribute "kind" in definition of source %s (see section [source.%s] in configuration). Falling back to kind "test".', kind, name, name)
33 sources[name] = TestSource(name, has_audio, has_video)
2234
23 if kind == 'decklink':
24 sources[name] = DeckLinkAVSource(name, outputs, has_audio, has_video)
25 return sources[name]
26
27 if kind != 'tcp':
28 log.warning('Unknown source kind "%s", defaulting to "tcp"', kind)
29
30 sources[name] = TCPAVSource(name, port, outputs,
31 has_audio, has_video,
32 force_num_streams)
3335 return sources[name]
3436
3537
38 def kind_has_audio(source):
39 return source in ["decklink", "tcp", "test"]
40
41
3642 def restart_source(name):
37 sources[name].restart()
43 assert False, "restart_source() not implemented"
0 #!/usr/bin/env python3
01 import logging
12 from abc import ABCMeta, abstractmethod
2
3 from gi.repository import Gst
3 from gi.repository import GLib
44
55 from lib.config import Config
6 from lib.clock import Clock
6 from lib.args import Args
77
88
99 class AVSource(object, metaclass=ABCMeta):
10 def __init__(self, name, outputs=None,
11 has_audio=True, has_video=True,
12 force_num_streams=None):
13 if not self.log:
14 self.log = logging.getLogger('AVSource[{}]'.format(name))
15
16 if outputs is None:
17 outputs = [name]
18
10
11 def __init__(self,
12 class_name,
13 name,
14 has_audio=True,
15 has_video=True,
16 num_streams=None,
17 show_no_signal=False):
18 # create logging interface
19 self.log = logging.getLogger("%s[%s]" % (class_name, name))
20
21 # make sure we have at least something
1922 assert has_audio or has_video
2023
24 # remember things
25 self.class_name = class_name
2126 self.name = name
2227 self.has_audio = has_audio
2328 self.has_video = has_video
24 self.outputs = outputs
25 self.force_num_streams = force_num_streams
26 self.pipeline = None
27
29 # fetch audio streams from config (different for blinder source)
30 if name == "blinder":
31 self.audio_streams = Config.getBlinderAudioStreams()
32 else:
33 self.audio_streams = Config.getAudioStreams()
34 # remember if we shall show no-signal underlay
35 self.show_no_signal = show_no_signal and Config.getNoSignal()
36
37 # maybe initialize no signal watch dog
38 if self.show_no_signal:
39 # check if we have video to show no-signal message
40 assert self.has_video
41 # set timeout at which we check for signal loss
42 GLib.timeout_add(self.timer_resolution * 1000, self.do_timeout)
43 # this might get attached to the no-signal compositor's input sink
44 self.noSignalSink = None
45
46 @abstractmethod
2847 def __str__(self):
29 return 'AVSource[{name}]'.format(
30 name=self.name
31 )
32
33 def build_pipeline(self, pipeline):
34 if self.has_audio:
35 num_streams = self.force_num_streams
36 if num_streams is None:
37 num_streams = Config.getint('mix', 'audiostreams')
38
39 for audiostream in range(0, num_streams):
40 audioport = self.build_audioport(audiostream)
41 if not audioport:
42 continue
43
44 pipeline += """
45 {audioport} !
46 {acaps} !
47 queue !
48 tee name=atee_stream{audiostream}
49 """.format(
48 raise NotImplementedError(
49 '__str__ not implemented for this source')
50
51 def attach(self, pipeline):
52 if self.show_no_signal:
53 # attach self.noSignalSink to no-signal compositor
54 self.noSignalSink = pipeline.get_by_name(
55 'compositor-{}'.format(self.name)).get_static_pad('sink_1')
56
57 def build_pipeline(self):
58 self.bin = "" if Args.no_bins else """
59 bin.(
60 name={class_name}-{name}
61 """.format(class_name=self.class_name, name=self.name)
62
63 self.bin += self.build_source()
64
65 if self.internal_audio_channels():
66 audioport = self.build_audioport()
67 if audioport:
68 audio_streams = self.audio_streams.get_stream_names(self.name)
69 self.bin += """
70 {audioport}
71 ! queue
72 max-size-time=3000000000
73 name=queue-source-audio-{name}
74 ! tee
75 name=source-audio-{name}
76 """.format(
5077 audioport=audioport,
51 acaps=Config.get('mix', 'audiocaps'),
52 audiostream=audiostream,
78 name=self.name
5379 )
54
55 for output in self.outputs:
56 pipeline += """
57 atee_stream{audiostream}. ! queue ! interaudiosink
58 channel=audio_{output}_stream{audiostream}
59 """.format(
60 output=output,
61 audiostream=audiostream,
80 if not audio_streams:
81 self.bin += """
82 source-audio-{name}.
83 ! queue
84 max-size-time=3000000000
85 name=queue-source-audio-fakesink-{name}
86 ! fakesink
87 async=false
88 """.format(name=self.name)
89
90 for stream in audio_streams:
91 self.log.info("Creating audio streams '{}' from source '{}'".format(stream,self.name))
92 self.bin += """
93 source-audio-{name}.
94 ! queue
95 max-size-time=3000000000
96 name=queue-audiomixmatrix-{stream}
97 ! audiomixmatrix
98 name=audiomixmatrix-{stream}
99 in-channels={in_channels}
100 out-channels={out_channels}
101 matrix="{matrix}"
102 ! {acaps}
103 ! queue
104 name=queue-audio-{stream}
105 max-size-time=3000000000
106 ! tee
107 name=audio-{stream}
108 """.format(
109 in_channels=self.internal_audio_channels(),
110 out_channels=Config.getAudioChannels(),
111 matrix=str(self.audio_streams.matrix(self.name,
112 stream,
113 Config.getAudioChannels(),
114 grid=self.get_valid_channel_numbers())
115 ).replace("[", "<").replace("]", ">"),
116 acaps=Config.getAudioCaps(),
117 stream=stream,
118 name=self.name
62119 )
63120
64121 if self.has_video:
65 pipeline += """
66 {videoport} !
67 {vcaps} !
68 queue !
69 tee name=vtee
70 """.format(
122 if self.show_no_signal and Config.getNoSignal():
123 video = """
124 videotestsrc
125 name=canvas-{name}
126 pattern={nosignalpattern}
127 ! textoverlay
128 name=nosignal-{name}
129 text=\"{nosignal}\"
130 valignment=center
131 halignment=center
132 shaded-background=yes
133 font-desc="Roboto Bold, 20"
134 ! {vcaps}
135 ! queue
136 max-size-time=3000000000
137 ! compositor-{name}.
138
139 {videoport}
140 ! {vcaps}
141 ! queue
142 max-size-time=3000000000
143 ! compositor-{name}.
144
145 compositor
146 name=compositor-{name}
147 ! queue
148 max-size-time=3000000000
149 ! tee
150 name=video-{name}"""
151 else:
152 video = """
153 {videoport}
154 ! {vcaps}
155 ! queue
156 max-size-time=3000000000
157 ! tee
158 name=video-{name}"""
159 self.bin += video.format(
71160 videoport=self.build_videoport(),
72 deinterlacer=self.build_deinterlacer(),
73 vcaps=Config.get('mix', 'videocaps')
161 name=self.name,
162 vcaps=Config.getVideoCaps(),
163 nosignal=self.get_nosignal_text(),
164 nosignalpattern=Config.getNoSignal()
74165 )
75
76 for output in self.outputs:
77 pipeline += """
78 vtee. ! queue ! intervideosink channel=video_{output}
79 """.format(
80 output=output
81 )
82
83 self.log.debug('Launching Source-Pipeline:\n%s', pipeline)
84 self.pipeline = Gst.parse_launch(pipeline)
85 self.pipeline.use_clock(Clock)
86
87 self.log.debug('Binding End-of-Stream-Signal on Source-Pipeline')
88 self.pipeline.bus.add_signal_watch()
89 self.pipeline.bus.connect("message::eos", self.on_eos)
90 self.pipeline.bus.connect("message::error", self.on_error)
166 self.bin += "" if Args.no_bins else """
167 )
168 """
169
170 self.bin = self.bin
171
172 def build_source(self):
173 return ""
91174
92175 def build_deinterlacer(self):
93 deinterlace_config = self.get_deinterlace_config()
94
95 if deinterlace_config == "yes":
176 source_mode = Config.getSourceScan(self.name)
177
178 if source_mode == "interlaced":
96179 return "videoconvert ! yadif mode=interlaced"
97
98 elif deinterlace_config == "assume-progressive":
180 elif source_mode == "psf":
99181 return "capssetter " \
100182 "caps=video/x-raw,interlace-mode=progressive"
101
102 elif deinterlace_config == "no":
103 return ""
104
183 elif source_mode == "progressive":
184 return None
105185 else:
106186 raise RuntimeError(
107187 "Unknown Deinterlace-Mode on source {} configured: {}".
108 format(self.name, deinterlace_config))
109
110 def get_deinterlace_config(self):
111 section = 'source.{}'.format(self.name)
112 deinterlace_config = Config.get(section, 'deinterlace', fallback="no")
113 return deinterlace_config
114
115 def on_eos(self, bus, message):
116 self.log.debug('Received End-of-Stream-Signal on Source-Pipeline')
117
118 def on_error(self, bus, message):
119 self.log.warning('Received Error-Signal on Source-Pipeline')
120 (error, debug) = message.parse_error()
121 self.log.debug('Error-Details: #%u: %s', error.code, debug)
188 format(self.name, source_mode))
189
190 def video_channels(self):
191 return 1 if self.has_video else 0
192
193 def audio_channels(self):
194 return self.audio_streams.num_channels(self.name) if self.has_audio else 0
195
196 def internal_audio_channels(self):
197 return self.audio_streams.num_channels(self.name, self.get_valid_channel_numbers()) if self.has_audio else 0
198
199 def get_valid_channel_numbers(self):
200 return [x for x in range(1, 255)]
201
202 def num_connections(self):
203 return 0
204
205 def is_input(self):
206 return True
207
208 def section(self):
209 return 'source.{}'.format(self.name)
122210
123211 @abstractmethod
124 def build_audioport(self, audiostream):
125 raise NotImplementedError(
126 'build_audioport not implemented for this source')
127
128 @abstractmethod
212 def port(self):
213 raise NotImplementedError("port() not implemented in %s" % self.name)
214
215 def build_audioport(self):
216 raise None
217
129218 def build_videoport(self):
130 raise NotImplementedError(
131 'build_videoport not implemented for this source')
132
133 @abstractmethod
134 def restart(self):
135 raise NotImplementedError('Restarting not implemented for this source')
219 raise None
220
221 def get_nosignal_text(self):
222 return "NO SIGNAL\n" + self.name.upper()
223
224 def do_timeout(self):
225 if self.noSignalSink:
226 self.noSignalSink.set_property(
227 'alpha', 1.0 if self.num_connections() > 0 else 0.0)
228 # just come back
229 return True
0 #!/usr/bin/env python3
01 import logging
12 import re
2
3 from gi.repository import Gst
43
54 from lib.config import Config
65 from lib.sources.avsource import AVSource
87
98 class DeckLinkAVSource(AVSource):
109
11 def __init__(self, name, outputs=None, has_audio=True, has_video=True):
12 self.log = logging.getLogger('DecklinkAVSource[{}]'.format(name))
13 super().__init__(name, outputs, has_audio, has_video)
10 timer_resolution = 0.5
1411
15 section = 'source.{}'.format(name)
12 def __init__(self, name, has_audio=True, has_video=True):
13 super().__init__('DecklinkAVSource', name, has_audio, has_video, show_no_signal=True)
1614
17 # Device number, default: 0
18 self.device = Config.get(section, 'devicenumber', fallback=0)
15 self.device = Config.getDeckLinkDeviceNumber(name)
16 self.aconn = Config.getDeckLinkAudioConnection(name)
17 self.vconn = Config.getDeckLinkVideoConnection(name)
18 self.vmode = Config.getDeckLinkVideoMode(name)
19 self.vfmt = Config.getDeckLinkVideoFormat(name)
20 self.name = name
1921
20 # Audio connection, default: Automatic
21 self.aconn = Config.get(section, 'audio_connection', fallback='auto')
22 self.signalPad = None
23 self.build_pipeline()
2224
23 # Video connection, default: Automatic
24 self.vconn = Config.get(section, 'video_connection', fallback='auto')
25 def port(self):
26 return "Decklink #{}".format(self.device)
2527
26 # Video mode, default: 1080i50
27 self.vmode = Config.get(section, 'video_mode', fallback='1080i50')
28 def attach(self, pipeline):
29 super().attach(pipeline)
30 self.signalPad = pipeline.get_by_name(
31 'decklinkvideosrc-{}'.format(self.name))
2832
29 # Video format, default: auto
30 self.vfmt = Config.get(section, 'video_format', fallback='auto')
33 def num_connections(self):
34 return 1 if self.signalPad and self.signalPad.get_property('signal') else 0
3135
32 self.audiostream_map = self._parse_audiostream_map(section)
33 self.log.info("audiostream_map: %s", self.audiostream_map)
34
35 self.fallback_default = False
36 if len(self.audiostream_map) == 0:
37 self.log.info("no audiostream-mapping defined,"
38 "defaulting to mapping channel 0+1 to first stream")
39 self.fallback_default = True
40
41 self._warn_incorrect_number_of_streams()
42
43 self.required_input_channels = \
44 self._calculate_required_input_channels()
45
46 self.log.info("configuring decklink-input to %u channels",
47 self.required_input_channels)
48
49 min_gst_multi_channels = (1, 12, 3)
50 if self.required_input_channels > 2 and \
51 Gst.version() < min_gst_multi_channels:
52
53 self.log.warning(
54 'GStreamer version %s is probably too to use more then 2 '
55 'channels on your decklink source. officially supported '
56 'since %s',
57 tuple(Gst.version()), min_gst_multi_channels)
58
59 self.launch_pipeline()
60
61 def _calculate_required_input_channels(self):
62 required_input_channels = 0
63 for audiostream, mapping in self.audiostream_map.items():
64 left, right = self._parse_audiostream_mapping(mapping)
65 required_input_channels = max(required_input_channels, left + 1)
66 if right:
67 required_input_channels = max(required_input_channels,
68 right + 1)
69
70 required_input_channels = \
71 self._round_decklink_channels(required_input_channels)
72
73 return required_input_channels
74
75 def _round_decklink_channels(self, required_input_channels):
76 if required_input_channels > 16:
77 raise RuntimeError(
78 "Decklink-Devices support up to 16 Channels,"
79 "you requested {}".format(required_input_channels))
80
81 elif required_input_channels > 8:
82 required_input_channels = 16
83
84 elif required_input_channels > 2:
85 required_input_channels = 8
86
87 else:
88 required_input_channels = 2
89
90 return required_input_channels
91
92 def _parse_audiostream_map(self, config_section):
93 audiostream_map = {}
94
95 if config_section not in Config:
96 return audiostream_map
97
98 for key in Config[config_section]:
99 value = Config.get(config_section, key)
100 m = re.match(r'audiostream\[(\d+)\]', key)
101 if m:
102 audiostream = int(m.group(1))
103 audiostream_map[audiostream] = value
104
105 return audiostream_map
106
107 def _parse_audiostream_mapping(self, mapping):
108 m = re.match(r'(\d+)\+(\d+)', mapping)
109 if m:
110 return (int(m.group(1)), int(m.group(2)),)
111 else:
112 return (int(mapping), None,)
113
114 def _warn_incorrect_number_of_streams(self):
115 num_streams = Config.getint('mix', 'audiostreams')
116 for audiostream, mapping in self.audiostream_map.items():
117 if audiostream >= num_streams:
118 raise RuntimeError(
119 "Mapping-Configuration for Stream 0 to {} found,"
120 "but only {} enabled"
121 .format(audiostream, num_streams))
36 def get_valid_channel_numbers(self):
37 return (2, 8, 16)
12238
12339 def __str__(self):
12440 return 'DecklinkAVSource[{name}] reading card #{device}'.format(
12642 device=self.device
12743 )
12844
129 def launch_pipeline(self):
45 def build_source(self):
13046 # A video source is required even when we only need audio
131 pipeline = """
47 pipe = """
13248 decklinkvideosrc
49 name=decklinkvideosrc-{name}
13350 device-number={device}
13451 connection={conn}
13552 video-format={fmt}
136 mode={mode} !
137 """.format(
138 device=self.device,
139 conn=self.vconn,
140 mode=self.vmode,
141 fmt=self.vfmt
142 )
53 mode={mode}
54 """.format(name=self.name,
55 device=self.device,
56 conn=self.vconn,
57 mode=self.vmode,
58 fmt=self.vfmt
59 )
14360
61 # add rest of the video pipeline
14462 if self.has_video:
145 pipeline += """
146 {deinterlacer}
147 videoconvert !
148 videoscale !
149 videorate name=vout
150 """.format(
151 deinterlacer=self.build_deinterlacer()
63 # maybe add deinterlacer
64 if self.build_deinterlacer():
65 pipe += """\
66 ! {deinterlacer}
67 """.format(deinterlacer=self.build_deinterlacer())
68
69 pipe += """\
70 ! videoconvert
71 ! videoscale
72 ! videorate
73 name=vout-{name}
74 """.format(
75 deinterlacer=self.build_deinterlacer(),
76 name=self.name
15277 )
15378 else:
154 pipeline += """
155 fakesink
156 """
79 pipe += """\
80 ! fakesink
81 """
15782
158 if self.has_audio:
159 pipeline += """
83 if self.internal_audio_channels():
84 pipe += """
16085 decklinkaudiosrc
161 {channels}
86 name=decklinkaudiosrc-{name}
16287 device-number={device}
16388 connection={conn}
164 {output}
165 """.format(
166 channels="channels={}".format(self.required_input_channels)
167 if self.required_input_channels > 2 else
168 "",
169 device=self.device,
170 conn=self.aconn,
171 output="name=aout"
172 if self.fallback_default else
173 "! deinterleave name=aout",
174 )
89 channels={channels}
90 """.format(name=self.name,
91 device=self.device,
92 conn=self.aconn,
93 channels=self.internal_audio_channels())
17594
176 for audiostream, mapping in self.audiostream_map.items():
177 left, right = self._parse_audiostream_mapping(mapping)
178 if right is not None:
179 self.log.info(
180 "mapping decklink input-channels {left} and {right}"
181 "as left and right to output-stream {audiostream}"
182 .format(left=left,
183 right=right,
184 audiostream=audiostream))
95 return pipe
18596
186 pipeline += """
187 interleave name=i{audiostream}
188
189 aout.src_{left} ! queue ! i{audiostream}.sink_0
190 aout.src_{right} ! queue ! i{audiostream}.sink_1
191 """.format(
192 left=left,
193 right=right,
194 audiostream=audiostream
195 )
196 else:
197 self.log.info(
198 "mapping decklink input-channel {channel} "
199 "as left and right to output-stream {audiostream}"
200 .format(channel=left,
201 audiostream=audiostream))
202
203 pipeline += """
204 interleave name=i{audiostream}
205 aout.src_{channel} ! tee name=t{audiostream}
206
207 t{audiostream}. ! queue ! i{audiostream}.sink_0
208 t{audiostream}. ! queue ! i{audiostream}.sink_1
209 """.format(
210 channel=left,
211 audiostream=audiostream
212 )
213
214 self.build_pipeline(pipeline)
215 self.pipeline.set_state(Gst.State.PLAYING)
216
217 def build_deinterlacer(self):
218 deinterlacer = super().build_deinterlacer()
219 if deinterlacer != '':
220 deinterlacer += ' !'
221
222 return deinterlacer
223
224 def build_audioport(self, audiostream):
225 if self.fallback_default and audiostream == 0:
226 return "aout."
227
228 if audiostream in self.audiostream_map:
229 return 'i{}.'.format(audiostream)
97 def build_audioport(self):
98 return 'decklinkaudiosrc-{name}.'.format(name=self.name)
23099
231100 def build_videoport(self):
232 return 'vout.'
101 return 'vout-{}.'.format(self.name)
233102
234 def restart(self):
235 self.pipeline.set_state(Gst.State.NULL)
236 self.launch_pipeline()
103 def get_nosignal_text(self):
104 return super().get_nosignal_text() + "/BM%d" % self.device
0 #!/usr/bin/env python3
1 import logging
2 import re
3
4 import os
5
6 from gi.repository import Gst
7
8 from lib.config import Config
9 from lib.sources.avsource import AVSource
10
11 class FileSource(AVSource):
12 timer_resolution = 0.5
13
14 def __init__(self, name, has_audio=True, has_video=True,
15 force_num_streams=None):
16 self.location = Config.getLocation(name)
17 self.audio_file = False
18 (_, ext) = os.path.splitext(self.location)
19 if ext in ['.mp2','.mp3']:
20 assert not has_video
21 self.audio_file=True
22
23 super().__init__('FileSource', name, has_audio, has_video, show_no_signal=False)
24 self.loop = Config.getLoop(name)
25 self.build_pipeline()
26
27 def __str__(self):
28 return 'FileSource[{name}] displaying {location}'.format(
29 name=self.name,
30 location=self.location
31 )
32
33 def port(self):
34 return os.path.basename(self.location)
35
36 def num_connections(self):
37 return 1
38
39 def video_channels(self):
40 return 1
41
42 def build_source(self):
43 source = """
44 multifilesrc
45 location={location}
46 loop={loop}""".format(
47 loop=self.loop,
48 location=self.location
49 )
50 if not self.audio_file:
51 source += """
52 ! tsdemux
53 """
54 source += """
55 name=file-{name}
56 """.format(name=self.name)
57
58 return source
59
60 def build_videoport(self):
61 return """
62 file-{name}.
63 ! mpegvideoparse
64 ! mpeg2dec
65 ! videoconvert
66 ! videorate
67 ! videoscale
68 """.format(name=self.name)
69
70 def build_audioport(self):
71 return """
72 file-{name}.
73 ! mpegaudioparse
74 ! mpg123audiodec
75 ! audioconvert
76 ! audioresample
77 """.format(name=self.name)
0 #!/usr/bin/env python3
01 import logging
2 import re
3 import os
14
25 from gi.repository import Gst
36
69
710
811 class ImgVSource(AVSource):
9 def __init__(self, name, outputs=None, has_audio=False, has_video=True):
10 self.log = logging.getLogger('ImgVSource[{}]'.format(name))
11 super().__init__(name, outputs, False, has_video)
12
13 if has_audio:
14 self.log.warning("Audio requested from video-only source")
15
16 section = 'source.{}'.format(name)
17 self.imguri = Config.get(section, 'imguri')
18
19 self.launch_pipeline()
12 def __init__(self, name):
13 super().__init__('ImgVSource', name, False, True)
14 self.imguri = Config.getImageURI(name)
15 self.build_pipeline()
2016
2117 def __str__(self):
2218 return 'ImgVSource[{name}] displaying {uri}'.format(
2420 uri=self.imguri
2521 )
2622
27 def launch_pipeline(self):
28 pipeline = """
29 uridecodebin uri={uri} !
30 videoconvert !
31 videoscale !
32 imagefreeze name=img
33 """.format(
23 def port(self):
24 m = re.search('.*/([^/]*)', self.imguri)
25 return self.imguri
26
27 def num_connections(self):
28 return 1
29
30 def video_channels(self):
31 return 1
32
33 def build_source(self):
34 return """
35 uridecodebin
36 name=imgvsrc-{name}
37 uri={uri}
38 ! videoconvert
39 ! videoscale
40 ! imagefreeze
41 name=img-{name}
42 """.format(
43 name=self.name,
3444 uri=self.imguri
3545 )
36 self.build_pipeline(pipeline)
37 self.pipeline.set_state(Gst.State.PLAYING)
38
39 def build_audioport(self, audiostream):
40 raise NotImplementedError(
41 'build_audioport not implemented for this source')
4246
4347 def build_videoport(self):
44 return 'img.'
48 return "img-{name}.".format(name=self.name)
4549
4650 def restart(self):
4751 self.pipeline.set_state(Gst.State.NULL)
0 #!/usr/bin/env python3
01 import logging
12
2 from gi.repository import Gst
3 from gi.repository import Gst, GObject
4 import socket
35
46 from lib.config import Config
57 from lib.sources.avsource import AVSource
6 from lib.tcpsingleconnection import TCPSingleConnection
78
89 ALL_AUDIO_CAPS = Gst.Caps.from_string('audio/x-raw')
910 ALL_VIDEO_CAPS = Gst.Caps.from_string('video/x-raw')
1011
1112
12 class TCPAVSource(AVSource, TCPSingleConnection):
13 def __init__(self, name, port, outputs=None,
14 has_audio=True, has_video=True,
13 class TCPAVSource(AVSource):
14 timer_resolution = 0.5
15
16 def __init__(self, name, listen_port, has_audio=True, has_video=True,
1517 force_num_streams=None):
16 self.log = logging.getLogger('TCPAVSource[{}]'.format(name))
17 AVSource.__init__(self, name, outputs,
18 has_audio, has_video,
19 force_num_streams)
20 TCPSingleConnection.__init__(self, port)
18 super().__init__('TCPAVSource', name, has_audio, has_video,
19 force_num_streams, show_no_signal=True)
20
21 self.listen_port = listen_port
22 self.tcpsrc = None
23 self.audio_caps = Gst.Caps.from_string(Config.getAudioCaps())
24 self.video_caps = Gst.Caps.from_string(Config.getVideoCaps())
25 self.build_pipeline()
26 self.connected = False
27
28 def port(self):
29 return"%s:%d" % (socket.gethostname(), self.listen_port)
30
31 def num_connections(self):
32 if self.connected:
33 return 1
34 else:
35 return 0
36
37 def attach(self, pipeline):
38 super().attach(pipeline)
39 self.log.debug("connecting to pads")
40
41 # create probe at static tcpserversrc.src to get EOS and trigger a restart
42 self.tcpsrc = pipeline.get_by_name(
43 'tcpsrc-{name}'.format(name=self.name))
44 self.tcpsrc.get_static_pad("src").add_probe(
45 Gst.PadProbeType.EVENT_DOWNSTREAM | Gst.PadProbeType.BLOCK, self.on_pad_event)
46
47 # subscribe to creation of dynamic pads in matroskademux
48 self.demux = pipeline.get_by_name(
49 'demux-{name}'.format(name=self.name))
50 self.demux.connect('pad-added', self.on_pad_added)
51
52 # remember queues the demux is connected to to reconnect them when necessary
53 self.queue_audio = pipeline.get_by_name(
54 'queue-tcpsrc-audio-{name}'.format(name=self.name))
55 self.queue_video = pipeline.get_by_name(
56 'queue-tcpsrc-video-{name}'.format(name=self.name))
57
58 self.src = pipeline.get_by_name('src-{name}'.format(name=self.name))
2159
2260 def __str__(self):
23 return 'TCPAVSource[{name}] on tcp-port {port}'.format(
61 return 'TCPAVSource[{name}] listening at {listen} ({port})'.format(
2462 name=self.name,
25 port=self.boundSocket.getsockname()[1]
63 listen=self.port(),
64 port=self.tcpsrc.get_property(
65 "current-port") if self.tcpsrc else "<disconnected>"
2666 )
2767
28 def on_accepted(self, conn, addr):
68 def build_source(self):
2969 deinterlacer = self.build_deinterlacer()
30 pipeline = """
31 fdsrc fd={fd} blocksize=1048576 !
32 queue !
33 matroskademux name=demux
34 """.format(
35 fd=conn.fileno()
70 pipe = """
71 tcpserversrc
72 name=tcpsrc-{name}
73 do-timestamp=TRUE
74 port={port}
75 ! demux-{name}.
76
77 matroskademux
78 name=demux-{name}
79 """.format(
80 name=self.name,
81 port=self.listen_port
3682 )
3783
3884 if deinterlacer:
39 pipeline += """
40 demux. !
41 video/x-raw !
42 {deinterlacer}
43 """.format(
44 deinterlacer=self.build_deinterlacer()
85 pipe += """
86 demux-{name}.video_0
87 ! queue
88 max-size-time=3000000000
89 name=queue-tcpsrc-video-{name}
90 ! video/x-raw
91 ! {deinterlacer}""".format(
92 name=self.name,
93 deinterlacer=deinterlacer
4594 )
46 self.build_pipeline(pipeline)
47
48 else:
49 self.build_pipeline(pipeline)
50
51 self.audio_caps = Gst.Caps.from_string(Config.get('mix', 'audiocaps'))
52 self.video_caps = Gst.Caps.from_string(Config.get('mix', 'videocaps'))
53
54 demux = self.pipeline.get_by_name('demux')
55 demux.connect('pad-added', self.on_pad_added)
56
57 self.pipeline.set_state(Gst.State.PLAYING)
95 return pipe
5896
5997 def build_deinterlacer(self):
6098 deinterlacer = super().build_deinterlacer()
99 if deinterlacer:
100 return deinterlacer + ' name=deinter-{name}'.format(name=self.name)
101 else:
102 return None
61103
62 if deinterlacer != '':
63 deinterlacer += ' name=deinter'
104 def on_pad_added(self, demux, pad):
105 caps = pad.query_caps(None)
106 self.log.debug('demuxer added pad w/ caps: %s', caps.to_string())
64107
65 return deinterlacer
66
67 def on_pad_added(self, demux, src_pad):
68 caps = src_pad.query_caps(None)
69 self.log.debug('demuxer added pad w/ caps: %s', caps.to_string())
70 if caps.can_intersect(ALL_AUDIO_CAPS):
108 if self.has_audio and caps.can_intersect(ALL_AUDIO_CAPS):
71109 self.log.debug('new demuxer-pad is an audio-pad, '
72110 'testing against configured audio-caps')
73111 if not caps.can_intersect(self.audio_caps):
78116 self.log.warning(' configured caps: %s',
79117 self.audio_caps.to_string())
80118
81 elif caps.can_intersect(ALL_VIDEO_CAPS):
119 elif self.has_video and caps.can_intersect(ALL_VIDEO_CAPS):
82120 self.log.debug('new demuxer-pad is a video-pad, '
83121 'testing against configured video-caps')
84122 if not caps.can_intersect(self.video_caps):
91129
92130 self.test_and_warn_interlace_mode(caps)
93131
94 def on_eos(self, bus, message):
95 super().on_eos(bus, message)
96 if self.currentConnection is not None:
97 self.disconnect()
132 # relink demux with following audio and video queues
133 if not pad.is_linked():
134 self.demux.link(self.queue_audio)
135 self.demux.link(self.queue_video)
136 self.connected = True
98137
99 def on_error(self, bus, message):
100 super().on_error(bus, message)
101 if self.currentConnection is not None:
102 self.disconnect()
138 def on_pad_event(self, pad, info):
139 if info.get_event().type == Gst.EventType.EOS:
140 self.log.warning('scheduling source restart')
141 self.connected = False
142 GObject.idle_add(self.restart)
103143
104 def disconnect(self):
105 self.pipeline.set_state(Gst.State.NULL)
106 self.pipeline = None
107 self.close_connection()
144 return Gst.PadProbeReturn.PASS
108145
109146 def restart(self):
110 if self.currentConnection is not None:
111 self.disconnect()
147 self.log.debug('restarting source \'%s\'', self.name)
148 self.tcpsrc.set_state(Gst.State.READY)
149 self.demux.set_state(Gst.State.READY)
150 self.demux.set_state(Gst.State.PLAYING)
151 self.tcpsrc.set_state(Gst.State.PLAYING)
112152
113 def build_audioport(self, audiostream):
114 return 'demux.audio_{}'.format(audiostream)
153 def build_audioport(self):
154 return """
155 ! queue
156 max-size-time=3000000000
157 name=queue-{name}.audio""".format(name=self.name)
115158
116159 def build_videoport(self):
117160 deinterlacer = self.build_deinterlacer()
118161 if deinterlacer:
119 return 'deinter.'
162 return """
163 deinter-{name}.""".format(name=self.name)
120164 else:
121 return 'demux.'
165 return """
166 demux-{name}.""".format(name=self.name)
122167
123168 def test_and_warn_interlace_mode(self, caps):
124169 interlace_mode = caps.get_structure(0).get_string('interlace-mode')
125 deinterlace_config = self.get_deinterlace_config()
170 source_mode = Config.getSourceScan(self.name)
126171
127 if interlace_mode == 'mixed' and deinterlace_config == 'no':
172 if interlace_mode == 'mixed' and source_mode == 'progressive':
128173 self.log.warning(
129174 'your source sent an interlace_mode-flag in the matroska-'
130175 'container, specifying the source-video-stream is of '
0 #!/usr/bin/env python3
1 import logging
2
3 from configparser import NoOptionError
4 from gi.repository import Gst
5
6 from lib.config import Config
7 from lib.sources.avsource import AVSource
8
9
10 class TestSource(AVSource):
11 def __init__(self, name, has_audio=True, has_video=True,
12 force_num_streams=None):
13 super().__init__('TestSource', name, has_audio, has_video,
14 force_num_streams)
15
16 self.name = name
17 self.pattern = Config.getTestPattern(name)
18 self.wave = Config.getTestWave(name)
19 self.build_pipeline()
20
21 def port(self):
22 if self.has_video:
23 if self.internal_audio_channels():
24 return "(AV:{}+{})".format(self.pattern, self.wave)
25 else:
26 return "(V:{})".format(self.pattern)
27 else:
28 if self.internal_audio_channels():
29 return "(A:{})".format(self.wave)
30 return "Test"
31
32 def num_connections(self):
33 return 1
34
35 def __str__(self):
36 return 'TestSource[{name}] ({pattern}, {wave})'.format(
37 name=self.name,
38 pattern=self.pattern,
39 wave=self.wave
40 )
41
42 def build_audioport(self):
43 # a volume of 0.126 is ~18dBFS
44 return """audiotestsrc
45 name=audiotestsrc-{name}
46 do-timestamp=true
47 freq=1000
48 volume=0.126
49 wave={wave}
50 is-live=true""".format(
51 name=self.name,
52 wave=self.wave,
53 )
54
55 def build_videoport(self):
56 return """videotestsrc
57 name=videotestsrc-{name}
58 do-timestamp=true
59 pattern={pattern}
60 is-live=true""".format(
61 name=self.name,
62 pattern=self.pattern
63 )
0 #!/usr/bin/env python3
1 import logging
2 import re
3
4 from gi.repository import Gst, GLib
5
6 from lib.config import Config
7 from lib.sources.avsource import AVSource
8
9
10 class V4l2AVSource(AVSource):
11
12 timer_resolution = 0.5
13
14 def __init__(self, name):
15 super().__init__('V4l2Source', name, False, True, show_no_signal=False)
16
17 self.device = Config.getV4l2Device(name)
18 self.width = Config.getV4l2Width(name)
19 self.height = Config.getV4l2Height(name)
20 self.framerate = Config.getV4l2Framerate(name)
21 self.format = Config.getV4l2Format(name)
22 self.name = name
23 self.signalPad = None
24
25 self.build_pipeline()
26
27 def port(self):
28 return "V4l2 device {}".format(self.device)
29
30 def attach(self, pipeline):
31 super().attach(pipeline)
32 self.signalPad = pipeline.get_by_name(
33 'v4l2videosrc-{}'.format(self.name))
34 GLib.timeout_add(self.timer_resolution * 1000, self.do_timeout)
35
36 def num_connections(self):
37 return 1 if self.signalPad and self.signalPad.get_property('signal') else 0
38
39 def __str__(self):
40 return 'V4l2AVSource[{name}] reading device {device}'.format(
41 name=self.name,
42 device=self.device
43 )
44
45 def get_valid_channel_numbers(self):
46 return (0)
47
48 def build_source(self):
49 pipe = """
50 v4l2src
51 device={device}
52 """.format(device=self.device)
53
54 pipe += """\
55 ! video/x-raw,width={width},height={height},format={format},framerate={framerate}
56 """.format(width=self.width,
57 height=self.height,
58 format=self.format,
59 framerate=self.framerate)
60
61 if self.build_deinterlacer():
62 pipe += """\
63 ! {deinterlacer}
64 """.format(deinterlacer=self.build_deinterlacer())
65
66 pipe += """\
67 ! videoscale
68 ! videoconvert
69 ! videorate
70 name=vout-{name}
71 """.format(name=self.name)
72
73 return pipe
74
75 def build_videoport(self):
76 return 'vout-{}.'.format(self.name)
+0
-201
voctocore/lib/streamblanker.py less more
0 import logging
1
2 from gi.repository import Gst
3
4 from lib.config import Config
5 from lib.clock import Clock
6
7
8 class StreamBlanker(object):
9 log = logging.getLogger('StreamBlanker')
10
11 def __init__(self):
12 self.acaps = Config.get('mix', 'audiocaps')
13 self.vcaps = Config.get('mix', 'videocaps')
14
15 self.names = Config.getlist('stream-blanker', 'sources')
16 self.log.info('Configuring StreamBlanker video %u Sources',
17 len(self.names))
18
19 self.volume = Config.getfloat('stream-blanker', 'volume')
20
21 # Videomixer
22 pipeline = """
23 compositor name=vmix !
24 {vcaps} !
25 queue !
26 intervideosink channel=video_stream-blanker_out
27 """.format(
28 vcaps=self.vcaps,
29 )
30
31 # Source from the Main-Mix
32 pipeline += """
33 intervideosrc channel=video_mix_stream-blanker !
34 {vcaps} !
35 vmix.
36 """.format(
37 vcaps=self.vcaps,
38 )
39
40 if Config.has_option('mix', 'slides_source_name'):
41 pipeline += """
42 compositor name=vmix-slides !
43 {vcaps} !
44 queue !
45 intervideosink channel=video_slides_stream-blanker_out
46 """.format(
47 vcaps=self.vcaps,
48 )
49
50 pipeline += """
51 intervideosrc channel=video_slides_stream-blanker !
52 {vcaps} !
53 vmix-slides.
54 """.format(
55 vcaps=self.vcaps,
56 )
57
58 for audiostream in range(0, Config.getint('mix', 'audiostreams')):
59 # Audiomixer
60 pipeline += """
61 audiomixer name=amix_{audiostream} !
62 {acaps} !
63 queue !
64 tee name=amix_{audiostream}-tee !
65 queue !
66 interaudiosink
67 channel=audio_stream-blanker_out_stream{audiostream}
68 """.format(
69 acaps=self.acaps,
70 audiostream=audiostream,
71 )
72
73 if Config.has_option('mix', 'slides_source_name'):
74 pipeline += """
75 amix_{audiostream}-tee. !
76 queue !
77 interaudiosink
78 channel=audio_slides_stream-blanker_out_stream{audiostream}
79 """.format(
80 audiostream=audiostream,
81 )
82
83 # Source from the Main-Mix
84 pipeline += """
85 interaudiosrc
86 channel=audio_mix_stream{audiostream}_stream-blanker !
87 {acaps} !
88 amix_{audiostream}.
89 """.format(
90 acaps=self.acaps,
91 audiostream=audiostream,
92 )
93
94 pipeline += "\n\n"
95
96 # Source from the Blank-Audio into a tee
97 pipeline += """
98 interaudiosrc channel=audio_stream-blanker_stream0 !
99 {acaps} !
100 queue !
101 tee name=atee
102 """.format(
103 acaps=self.acaps,
104 )
105
106 for audiostream in range(0, Config.getint('mix', 'audiostreams')):
107 # Source from the Blank-Audio-Tee into the Audiomixer
108 pipeline += """
109 atee. ! queue ! amix_{audiostream}.
110 """.format(
111 audiostream=audiostream,
112 )
113
114 pipeline += "\n\n"
115
116 for name in self.names:
117 # Source from the named Blank-Video
118 pipeline += """
119 intervideosrc channel=video_stream-blanker-{name} !
120 {vcaps} !
121 queue !
122 tee name=video_stream-blanker-tee-{name} !
123 queue !
124 vmix.
125 """.format(
126 name=name,
127 vcaps=self.vcaps,
128 )
129
130 if Config.has_option('mix', 'slides_source_name'):
131 pipeline += """
132 video_stream-blanker-tee-{name}. !
133 queue !
134 vmix-slides.
135 """.format(
136 name=name,
137 )
138
139 self.log.debug('Creating Mixing-Pipeline:\n%s', pipeline)
140 self.mixingPipeline = Gst.parse_launch(pipeline)
141 self.mixingPipeline.use_clock(Clock)
142
143 self.log.debug('Binding Error & End-of-Stream-Signal '
144 'on Mixing-Pipeline')
145 self.mixingPipeline.bus.add_signal_watch()
146 self.mixingPipeline.bus.connect("message::eos", self.on_eos)
147 self.mixingPipeline.bus.connect("message::error", self.on_error)
148
149 self.log.debug('Initializing Mixer-State')
150 self.blankSource = 0 if len(self.names) > 0 else None
151 self.applyMixerState()
152
153 self.log.debug('Launching Mixing-Pipeline')
154 self.mixingPipeline.set_state(Gst.State.PLAYING)
155
156 def on_eos(self, bus, message):
157 self.log.debug('Received End-of-Stream-Signal on Mixing-Pipeline')
158
159 def on_error(self, bus, message):
160 self.log.debug('Received Error-Signal on Mixing-Pipeline')
161 (error, debug) = message.parse_error()
162 self.log.debug('Error-Details: #%u: %s', error.code, debug)
163
164 def applyMixerState(self):
165 self.applyMixerStateAudio()
166 self.applyMixerStateVideo('vmix')
167 if Config.has_option('mix', 'slides_source_name'):
168 self.applyMixerStateVideo('vmix-slides')
169
170 def applyMixerStateAudio(self):
171 is_blanked = self.blankSource is not None
172
173 for audiostream in range(0, Config.getint('mix', 'audiostreams')):
174 mixer = self.mixingPipeline.get_by_name(
175 'amix_{}'.format(audiostream))
176 mixpad = mixer.get_static_pad('sink_0')
177 blankpad = mixer.get_static_pad('sink_1')
178
179 mixpad.set_property(
180 'volume',
181 0.0 if is_blanked else 1.0)
182
183 blankpad.set_property(
184 'volume',
185 self.volume if is_blanked else 0.0)
186
187 def applyMixerStateVideo(self, mixername):
188 mixpad = (self.mixingPipeline.get_by_name(mixername)
189 .get_static_pad('sink_0'))
190 mixpad.set_property('alpha', int(self.blankSource is None))
191
192 for idx, name in enumerate(self.names):
193 blankpad = (self.mixingPipeline
194 .get_by_name(mixername)
195 .get_static_pad('sink_%u' % (idx + 1)))
196 blankpad.set_property('alpha', int(self.blankSource == idx))
197
198 def setBlankSource(self, source):
199 self.blankSource = source
200 self.applyMixerState()
00 import logging
11 import socket
2 import sys
23 from queue import Queue
34 from abc import ABCMeta, abstractmethod
45 from gi.repository import GObject
1011 if not hasattr(self, 'log'):
1112 self.log = logging.getLogger('TCPMultiConnection')
1213
13 self.boundSocket = None
14 self.currentConnections = dict()
14 self._port = None
1515
16 self.log.debug('Binding to Source-Socket on [::]:%u', port)
17 self.boundSocket = socket.socket(socket.AF_INET6)
18 self.boundSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
19 self.boundSocket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY,
20 False)
21 self.boundSocket.bind(('::', port))
22 self.boundSocket.listen(1)
16 try:
17 self.boundSocket = None
18 self.currentConnections = dict()
2319
24 self.log.debug('Setting GObject io-watch on Socket')
25 GObject.io_add_watch(self.boundSocket, GObject.IO_IN, self.on_connect)
20 self.log.debug('Binding to Source-Socket on [::]:%u', port)
21 self.boundSocket = socket.socket(socket.AF_INET6)
22 self.boundSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
23 self.boundSocket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY,
24 False)
25 self.boundSocket.bind(('::', port))
26 self.boundSocket.listen(1)
27 self._port = port
28
29 self.log.debug('Setting GObject io-watch on Socket')
30 GObject.io_add_watch(self.boundSocket, GObject.IO_IN, self.on_connect)
31 except OSError:
32 self.log.error("Can not open listening port %d because it is already in use. Is another instance of voctocore running already?" % port)
33 sys.exit(-1)
34
35
36 def port(self):
37 return "%s:%d" % (socket.gethostname(), self._port if self._port else 0)
38
39 def num_connections(self):
40 return len(self.currentConnections)
41
42 def is_input(self):
43 return False
2644
2745 def on_connect(self, sock, *args):
2846 conn, addr = sock.accept()
2947 conn.setblocking(False)
3048
31 self.log.info("Incomming Connection from [%s]:%u (fd=%u)",
49 self.log.info("Incoming Connection from [%s]:%u (fd=%u)",
3250 addr[0], addr[1], conn.fileno())
3351
3452 self.currentConnections[conn] = Queue()
35 self.log.info('Now %u Receiver connected',
53 self.log.info('Now %u Receiver(s) connected',
3654 len(self.currentConnections))
3755
3856 self.on_accepted(conn, addr)
+0
-58
voctocore/lib/tcpsingleconnection.py less more
0 import logging
1 import socket
2 import time
3 from abc import ABCMeta, abstractmethod
4 from gi.repository import GObject
5
6
7 class TCPSingleConnection(object, metaclass=ABCMeta):
8
9 def __init__(self, port):
10 if not hasattr(self, 'log'):
11 self.log = logging.getLogger('TCPSingleConnection')
12
13 self.log.debug('Binding to Source-Socket on [::]:%u', port)
14 self.boundSocket = socket.socket(socket.AF_INET6)
15 self.boundSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
16 self.boundSocket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY,
17 False)
18 self.boundSocket.bind(('::', port))
19 self.boundSocket.listen(1)
20
21 self.currentConnection = None
22
23 self.log.debug('Setting GObject io-watch on Socket')
24 GObject.io_add_watch(self.boundSocket, GObject.IO_IN, self.on_connect)
25
26 def on_connect(self, sock, *args):
27 conn, addr = sock.accept()
28 self.log.info('Incomming Connection from %s', addr)
29
30 if self.currentConnection is not None:
31 self.log.warning('Another Source is already connected, '
32 'closing existing pipeline')
33 self.disconnect()
34 time.sleep(1)
35
36 self.on_accepted(conn, addr)
37 self.currentConnection = conn
38
39 return True
40
41 def close_connection(self):
42 if self.currentConnection:
43 self.currentConnection.close()
44 self.currentConnection = None
45 self.log.info('Connection closed')
46
47 @abstractmethod
48 def on_accepted(self, conn, addr):
49 raise NotImplementedError(
50 "child classes of TCPSingleConnection must implement on_accepted()"
51 )
52
53 @abstractmethod
54 def disconnect(self):
55 raise NotImplementedError(
56 "child classes of TCPSingleConnection must implement disconnect()"
57 )
0 #!/usr/bin/env python3
01 import logging
2
13 from configparser import NoOptionError
24 from enum import Enum, unique
3
5 import gi
6 gi.require_version('GstController', '1.0')
47 from gi.repository import Gst
5
68 from lib.config import Config
7 from lib.clock import Clock
8
9
10 @unique
11 class CompositeModes(Enum):
12 fullscreen = 0
13 side_by_side_equal = 1
14 side_by_side_preview = 2
15 picture_in_picture = 3
16
17
18 class PadState(object):
19 def __init__(self):
20 self.reset()
21
22 def reset(self):
23 self.alpha = 1.0
24
25 self.zorder = 1
26
27 self.xpos = 0
28 self.ypos = 0
29
30 self.width = 0
31 self.height = 0
32
33 self.croptop = 0
34 self.cropleft = 0
35 self.cropbottom = 0
36 self.cropright = 0
9 from vocto.transitions import Composites, Transitions, Frame, fade_alpha
10 from lib.scene import Scene
11 from lib.overlay import Overlay
12 from lib.args import Args
13
14 from vocto.composite_commands import CompositeCommand
3715
3816
3917 class VideoMix(object):
4018 log = logging.getLogger('VideoMix')
4119
4220 def __init__(self):
43 self.caps = Config.get('mix', 'videocaps')
44
45 self.names = Config.getlist('mix', 'sources')
46 self.log.info('Configuring Mixer for %u Sources', len(self.names))
47
48 pipeline = """
49 compositor name=mix !
50 {caps} !
51 identity name=sig !
52 queue !
53 tee name=tee
54
55 intervideosrc channel=video_background !
56 {caps} !
57 mix.
58
59 tee. ! queue ! intervideosink channel=video_mix_out
60 """.format(
61 caps=self.caps
21 # read sources from confg file
22 self.bgSources = Config.getBackgroundSources()
23 self.sources = Config.getSources()
24 self.log.info('Configuring mixer for %u source(s) and %u background source(s)', len(self.sources), len(self.bgSources))
25
26 # load composites from config
27 self.log.info("Reading transitions configuration...")
28 self.composites = Config.getComposites()
29
30 # load transitions from configuration
31 self.transitions = Config.getTransitions(self.composites)
32 self.scene = None
33 self.bgScene = None
34 self.overlay = None
35
36 Config.getAudioStreams()
37
38 # build GStreamer mixing pipeline descriptor
39 self.bin = "" if Args.no_bins else """
40 bin.(
41 name=VideoMix
42 """
43 self.bin += """
44 compositor
45 name=videomixer
46 """
47 if Config.hasOverlay():
48 self.bin += """\
49 ! queue
50 max-size-time=3000000000
51 name=queue-overlay
52 ! gdkpixbufoverlay
53 name=overlay
54 overlay-width={width}
55 overlay-height={height}
56 """.format(
57 width=Config.getVideoResolution()[0],
58 height=Config.getVideoResolution()[1]
59 )
60 if Config.getOverlayFile():
61 self.bin += """\
62 location={overlay}
63 alpha=1.0
64 """.format(overlay=Config.getOverlayFilePath(Config.getOverlayFile()))
65 else:
66 self.log.info("No initial overlay source configured.")
67
68 self.bin += """\
69 ! identity
70 name=sig
71 ! {vcaps}
72 ! queue
73 max-size-time=3000000000
74 ! tee
75 name=video-mix
76 """.format(
77 vcaps=Config.getVideoCaps()
6278 )
6379
64 if Config.getboolean('previews', 'enabled'):
65 pipeline += """
66 tee. ! queue ! intervideosink channel=video_mix_preview
67 """
68
69 if Config.getboolean('stream-blanker', 'enabled'):
70 pipeline += """
71 tee. ! queue ! intervideosink channel=video_mix_stream-blanker
72 """
73
74 for idx, name in enumerate(self.names):
75 pipeline += """
76 intervideosrc channel=video_{name}_mixer !
77 {caps} !
78 videocrop name=video_{idx}_cropper !
79 mix.
80 """.format(
80 for idx, background in enumerate(self.bgSources):
81 self.bin += """
82 video-{name}.
83 ! queue
84 max-size-time=3000000000
85 name=queue-video-{name}
86 ! videomixer.
87 """.format(name=background)
88
89 for idx, name in enumerate(self.sources):
90 self.bin += """
91 video-{name}.
92 ! queue
93 max-size-time=3000000000
94 name=queue-cropper-{name}
95 ! videobox
96 name=cropper-{name}
97 ! queue
98 max-size-time=3000000000
99 name=queue-videomixer-{name}
100 ! videomixer.
101 """.format(
81102 name=name,
82 caps=self.caps,
83103 idx=idx
84104 )
85105
86 self.log.debug('Creating Mixing-Pipeline:\n%s', pipeline)
87 self.mixingPipeline = Gst.parse_launch(pipeline)
88 self.mixingPipeline.use_clock(Clock)
89
90 self.log.debug('Binding Error & End-of-Stream-Signal '
91 'on Mixing-Pipeline')
92 self.mixingPipeline.bus.add_signal_watch()
93 self.mixingPipeline.bus.connect("message::eos", self.on_eos)
94 self.mixingPipeline.bus.connect("message::error", self.on_error)
95
106 self.bin += "" if Args.no_bins else """)
107 """
108
109 def attach(self, pipeline):
96110 self.log.debug('Binding Handoff-Handler for '
97111 'Synchronus mixer manipulation')
98 sig = self.mixingPipeline.get_by_name('sig')
112 self.pipeline = pipeline
113 sig = pipeline.get_by_name('sig')
99114 sig.connect('handoff', self.on_handoff)
100115
101 self.padStateDirty = False
102 self.padState = list()
103 for idx, name in enumerate(self.names):
104 self.padState.append(PadState())
105
106116 self.log.debug('Initializing Mixer-State')
107 self.compositeMode = CompositeModes.fullscreen
108 self.sourceA = 0
109 self.sourceB = 1
110 self.recalculateMixerState()
111 self.applyMixerState()
112
113 bgMixerpad = (self.mixingPipeline.get_by_name('mix')
114 .get_static_pad('sink_0'))
115 bgMixerpad.set_property('zorder', 0)
116
117 self.log.debug('Launching Mixing-Pipeline')
118 self.mixingPipeline.set_state(Gst.State.PLAYING)
119
120 def getInputVideoSize(self):
121 caps = Gst.Caps.from_string(self.caps)
122 struct = caps.get_structure(0)
123 _, width = struct.get_int('width')
124 _, height = struct.get_int('height')
125
126 return width, height
127
128 def recalculateMixerState(self):
129 if self.compositeMode == CompositeModes.fullscreen:
130 self.recalculateMixerStateFullscreen()
131
132 elif self.compositeMode == CompositeModes.side_by_side_equal:
133 self.recalculateMixerStateSideBySideEqual()
134
135 elif self.compositeMode == CompositeModes.side_by_side_preview:
136 self.recalculateMixerStateSideBySidePreview()
137
138 elif self.compositeMode == CompositeModes.picture_in_picture:
139 self.recalculateMixerStatePictureInPicture()
140
141 self.log.debug('Marking Pad-State as Dirty')
142 self.padStateDirty = True
143
144 def recalculateMixerStateFullscreen(self):
145 self.log.info('Updating Mixer-State for Fullscreen-Composition')
146
147 for idx, name in enumerate(self.names):
148 pad = self.padState[idx]
149
150 pad.reset()
151 pad.alpha = float(idx == self.sourceA)
152
153 def recalculateMixerStateSideBySideEqual(self):
154 self.log.info('Updating Mixer-State for '
155 'Side-by-side-Equal-Composition')
156
157 width, height = self.getInputVideoSize()
158 self.log.debug('Video-Size parsed as %ux%u', width, height)
159
160 try:
161 gutter = Config.getint('side-by-side-equal', 'gutter')
162 self.log.debug('Gutter configured to %u', gutter)
163 except NoOptionError:
164 gutter = int(width / 100)
165 self.log.debug('Gutter calculated to %u', gutter)
166
167 try:
168 border = Config.getint('side-by-side-equal', 'border')
169 self.log.debug('border configured to %u', border)
170 except NoOptionError:
171 border = 0
172 self.log.debug('border calculated to %u', border)
173
174 targetWidth = int((width - gutter - border - border) / 2)
175 targetHeight = int(targetWidth / width * height)
176
177 self.log.debug('Video-Size calculated to %ux%u',
178 targetWidth, targetHeight)
179
180 xa = border
181 xb = width - targetWidth - border
182 y = int((height - targetHeight) / 2)
183
184 try:
185 ya = Config.getint('side-by-side-equal', 'atop')
186 self.log.debug('A-Video Y-Pos configured to %u', ya)
187 except NoOptionError:
188 ya = y
189 self.log.debug('A-Video Y-Pos calculated to %u', ya)
190
191 try:
192 yb = Config.getint('side-by-side-equal', 'btop')
193 self.log.debug('B-Video Y-Pos configured to %u', yb)
194 except NoOptionError:
195 yb = y
196 self.log.debug('B-Video Y-Pos calculated to %u', yb)
197
198 for idx, name in enumerate(self.names):
199 pad = self.padState[idx]
200 pad.reset()
201
202 pad.width = targetWidth
203 pad.height = targetHeight
204
205 if idx == self.sourceA:
206 pad.xpos = xa
207 pad.ypos = ya
208 pad.zorder = 2
209
210 elif idx == self.sourceB:
211 pad.xpos = xb
212 pad.ypos = yb
213 pad.zorder = 1
214
117 # initialize pipeline bindings for all sources
118 self.bgScene = Scene(self.bgSources, pipeline, self.transitions.fps, 0, cropping=False)
119 self.scene = Scene(self.sources, pipeline, self.transitions.fps, len(self.bgSources))
120 self.compositeMode = None
121 self.sourceA = None
122 self.sourceB = None
123 self.setCompositeEx(Composites.targets(self.composites)[
124 0].name, self.sources[0], self.sources[1])
125
126 if Config.hasOverlay():
127 self.overlay = Overlay(
128 pipeline, Config.getOverlayFile(), Config.getOverlayBlendTime())
129
130 def __str__(self):
131 return 'VideoMix'
132
133 def getPlayTime(self):
134 # get play time from mixing pipeline or assume zero
135 return self.pipeline.get_pipeline_clock().get_time() - \
136 self.pipeline.get_base_time()
137
138 def on_handoff(self, object, buffer):
139 playTime = self.getPlayTime()
140 if self.bgScene and self.bgScene.dirty:
141 # push background scene to gstreamer
142 self.log.debug('Applying new background at %d ms',
143 playTime / Gst.MSECOND)
144 self.bgScene.push(playTime)
145 if self.scene and self.scene.dirty:
146 # push scene to gstreamer
147 self.log.debug('Applying new mix at %d ms',
148 playTime / Gst.MSECOND)
149 self.scene.push(playTime)
150
151 def setCompositeEx(self, newCompositeName=None, newA=None, newB=None, useTransitions=False, dry=False):
152 # expect strings or None as parameters
153 assert not newCompositeName or type(newCompositeName) == str
154 assert not newA or type(newA) == str
155 assert not newB or type(newB) == str
156
157 # get current composite
158 if not self.compositeMode:
159 curCompositeName = None
160 self.log.info("Request composite %s(%s,%s)",
161 newCompositeName, newA, newB)
162 else:
163 curCompositeName = self.compositeMode
164 curA = self.sourceA
165 curB = self.sourceB
166 self.log.info("Request composite change from %s(%s,%s) to %s(%s,%s)",
167 curCompositeName, curA, curB, newCompositeName, newA, newB)
168
169 # check if there is any None parameter and fill it up with
170 # reasonable value from the current scene
171 if curCompositeName and not (newCompositeName and newA and newB):
172 # use current state if not defined by parameter
173 if not newCompositeName:
174 newCompositeName = curCompositeName
175 if not newA:
176 newA = curA if newB != curA else curB
177 if not newB:
178 newB = curA if newA == curB else curB
179 self.log.debug("Completing wildcarded composite to %s(%s,%s)",
180 newCompositeName, newA, newB)
181 # post condition: we should have all parameters now
182 assert newA != newB
183 assert newCompositeName and newA and newB
184
185 # fetch composites
186 curComposite = self.composites[curCompositeName] if curCompositeName else None
187 newComposite = self.composites[newCompositeName]
188
189 # if new scene is complete
190 if newComposite and newA in self.sources and newB in self.sources:
191 self.log.debug("New composite shall be %s(%s,%s)",
192 newComposite.name, newA, newB)
193 # try to find a matching transition from current to new scene
194 transition = None
195 targetA, targetB = newA, newB
196 if useTransitions:
197 if curComposite:
198 old = (curA,curB,newA,newB)
199
200 # check if whe have a three-channel scenario
201 if len(set(old)) == 3:
202 self.log.debug("Current composite includes three different frames: (%s,%s) -> (%s,%s)", *old)
203 # check if current composite hides B
204 if curComposite.single():
205 self.log.debug("Current composite hides channel B so we can secretly change it.")
206 # check for (A,B) -> (A,C)
207 if curA == newA:
208 # change into (A,C) -> (A,C)
209 curB = newB
210 # check for (A,B) -> (C,A)
211 elif curA == newB:
212 # change into (A,C) -> (C,A)
213 curB = newA
214 # check another case where new composite also hides B
215 elif newComposite.single():
216 self.log.debug("New composite also hides channel B so we can secretly change it.")
217 # change (A,B) -> (C,B) into (A,C) -> (C,A)
218 newB = curA
219 curB = newA
220 elif newComposite.single():
221 # check for (A,B) -> (A,C)
222 if curA == newA:
223 newB = curB
224 # check for (A,B) -> (B,C)
225 if curB == newA:
226 newB = curA
227
228 # check if whe have a four-channel scenario
229 if len(set(old)) == 4:
230 self.log.debug("Current composite includes four different frames: (%s,%s) -> (%s,%s)", *old)
231 # check if both composites hide channel B
232 if curComposite.single() and newComposite.single():
233 self.log.debug("Current and new composite hide channel B so we can secretly change it.")
234 # change (A,B) -> (C,D) into (A,C) -> (C,A)
235 curB = newA
236 newB = curA
237
238 # log if whe changed somtehing
239 if old != (curA,curB,newA,newB):
240 self.log.info("Changing requested transition from (%s,%s) -> (%s,%s) to (%s,%s) -> (%s,%s)", *old, curA,curB,newA,newB)
241
242 swap = False
243 if (curA, curB) == (newA, newB) and curComposite != newComposite:
244 transition, swap = self.transitions.solve(
245 curComposite, newComposite, False)
246 elif (curA, curB) == (newB, newA):
247 transition, swap = self.transitions.solve(
248 curComposite, newComposite, True)
249 if not swap:
250 targetA, targetB = newB, newA
251 if transition and not dry:
252 self.log.warning("No transition found")
253 if dry:
254 return (newA, newB) if transition else False
255 # z-orders of A and B
256 below = 100
257 above = 101
258 # found transition?
259 if transition:
260 # apply found transition
261 self.log.debug(
262 "committing transition '%s' to scene", transition.name())
263 self.scene.commit(targetA, transition.Az(below, above))
264 self.scene.commit(targetB, transition.Bz(above, below))
215265 else:
216 pad.alpha = 0
217
218 def recalculateMixerStateSideBySidePreview(self):
219 self.log.info('Updating Mixer-State for '
220 'Side-by-side-Preview-Composition')
221
222 width, height = self.getInputVideoSize()
223 self.log.debug('Video-Size parsed as %ux%u', width, height)
224
225 try:
226 asize = [int(i) for i in Config.get('side-by-side-preview',
227 'asize').split('x', 1)]
228 self.log.debug('A-Video-Size configured to %ux%u',
229 asize[0], asize[1])
230 except NoOptionError:
231 asize = [
232 int(width / 1.25), # 80%
233 int(height / 1.25) # 80%
234 ]
235 self.log.debug('A-Video-Size calculated to %ux%u',
236 asize[0], asize[1])
237
238 try:
239 acrop = [int(i) for i in Config.get('side-by-side-preview',
240 'acrop').split('/', 3)]
241 self.log.debug('A-Video-Cropping configured to %u/%u/%u/%u',
242 acrop[0], acrop[1], acrop[2], acrop[3])
243 except NoOptionError:
244 acrop = [0, 0, 0, 0]
245 self.log.debug('A-Video-Cropping calculated to %u/%u/%u/%u',
246 acrop[0], acrop[1], acrop[2], acrop[3])
247
248 try:
249 apos = [int(i) for i in Config.get('side-by-side-preview',
250 'apos').split('/', 1)]
251 self.log.debug('A-Video-Position configured to %u/%u',
252 apos[0], apos[1])
253 except NoOptionError:
254 apos = [
255 int(width / 100), # 1%
256 int(width / 100) # 1%
257 ]
258 self.log.debug('A-Video-Position calculated to %u/%u',
259 apos[0], apos[1])
260
261 try:
262 bsize = [int(i) for i in Config.get('side-by-side-preview',
263 'bsize').split('x', 1)]
264 self.log.debug('B-Video-Size configured to %ux%u',
265 bsize[0], bsize[1])
266 except NoOptionError:
267 bsize = [
268 int(width / 4), # 25%
269 int(height / 4) # 25%
270 ]
271 self.log.debug('B-Video-Size calculated to %ux%u',
272 bsize[0], bsize[1])
273
274 try:
275 bcrop = [int(i) for i in Config.get('side-by-side-preview',
276 'bcrop').split('/', 3)]
277 self.log.debug('B-Video-Cropping configured to %u/%u/%u/%u',
278 bcrop[0], bcrop[1], bcrop[2], bcrop[3])
279 except NoOptionError:
280 bcrop = [0, 0, 0, 0]
281 self.log.debug('B-Video-Cropping calculated to %u/%u/%u/%u',
282 bcrop[0], bcrop[1], bcrop[2], bcrop[3])
283
284 try:
285 bpos = [int(i) for i in Config.get('side-by-side-preview',
286 'bpos').split('/', 1)]
287 self.log.debug('B-Video-Position configured to %u/%u',
288 bpos[0], bpos[1])
289 except NoOptionError:
290 bpos = [
291 width - int(width / 100) - bsize[0],
292 height - int(width / 100) - bsize[1] # 1%
293 ]
294 self.log.debug('B-Video-Position calculated to %u/%u',
295 bpos[0], bpos[1])
296
297 for idx, name in enumerate(self.names):
298 pad = self.padState[idx]
299 pad.reset()
300
301 if idx == self.sourceA:
302 pad.xpos, pad.ypos = apos
303 pad.croptop, \
304 pad.cropleft, \
305 pad.cropbottom, \
306 pad.cropright = acrop
307 pad.width, pad.height = asize
308 pad.zorder = 1
309
310 elif idx == self.sourceB:
311 pad.xpos, pad.ypos = bpos
312 pad.croptop, \
313 pad.cropleft, \
314 pad.cropbottom, \
315 pad.cropright = bcrop
316 pad.width, pad.height = bsize
317 pad.zorder = 2
318
319 else:
320 pad.alpha = 0
321
322 def recalculateMixerStatePictureInPicture(self):
323 self.log.info('Updating Mixer-State for '
324 'Picture-in-Picture-Composition')
325
326 width, height = self.getInputVideoSize()
327 self.log.debug('Video-Size parsed as %ux%u', width, height)
328
329 try:
330 pipsize = [int(i) for i in Config.get('picture-in-picture',
331 'pipsize').split('x', 1)]
332 self.log.debug('PIP-Size configured to %ux%u',
333 pipsize[0], pipsize[1])
334 except NoOptionError:
335 pipsize = [
336 int(width / 4), # 25%
337 int(height / 4) # 25%
338 ]
339 self.log.debug('PIP-Size calculated to %ux%u',
340 pipsize[0], pipsize[1])
341
342 try:
343 pipcrop = [int(i) for i in Config.get('picture-in-picture',
344 'pipcrop').split('/', 3)]
345 self.log.debug('PIP-Video-Cropping configured to %u/%u/%u/%u',
346 pipcrop[0], pipcrop[1], pipcrop[2], pipcrop[3])
347 except NoOptionError:
348 pipcrop = [0, 0, 0, 0]
349 self.log.debug('PIP-Video-Cropping calculated to %u/%u/%u/%u',
350 pipcrop[0], pipcrop[1], pipcrop[2], pipcrop[3])
351
352 try:
353 pippos = [int(i) for i in Config.get('picture-in-picture',
354 'pippos').split('/', 1)]
355 self.log.debug('PIP-Position configured to %u/%u',
356 pippos[0], pippos[1])
357 except NoOptionError:
358 pippos = [
359 width - pipsize[0] - int(width / 100), # 1%
360 height - pipsize[1] - int(width / 100) # 1%
361 ]
362 self.log.debug('PIP-Position calculated to %u/%u',
363 pippos[0], pippos[1])
364
365 for idx, name in enumerate(self.names):
366 pad = self.padState[idx]
367 pad.reset()
368
369 if idx == self.sourceA:
370 pass
371 elif idx == self.sourceB:
372 pad.xpos, pad.ypos = pippos
373 pad.width, pad.height = pipsize
374 pad.zorder = 2
375
376 else:
377 pad.alpha = 0
378
379 def applyMixerState(self):
380 for idx, state in enumerate(self.padState):
381 # mixerpad 0 = background
382 mixerpad = (self.mixingPipeline
383 .get_by_name('mix')
384 .get_static_pad('sink_%u' % (idx + 1)))
385
386 cropper = self.mixingPipeline.get_by_name("video_%u_cropper" % idx)
387
388 self.log.debug('Reconfiguring Mixerpad %u to '
389 'x/y=%u/%u, w/h=%u/%u alpha=%0.2f, zorder=%u',
390 idx, state.xpos, state.ypos,
391 state.width, state.height,
392 state.alpha, state.zorder)
393 mixerpad.set_property('xpos', state.xpos)
394 mixerpad.set_property('ypos', state.ypos)
395 mixerpad.set_property('width', state.width)
396 mixerpad.set_property('height', state.height)
397 mixerpad.set_property('alpha', state.alpha)
398 mixerpad.set_property('zorder', state.zorder)
399
400 self.log.info("Reconfiguring Cropper %d to %d/%d/%d/%d",
401 idx,
402 state.croptop,
403 state.cropleft,
404 state.cropbottom,
405 state.cropright)
406 cropper.set_property("top", state.croptop)
407 cropper.set_property("left", state.cropleft)
408 cropper.set_property("bottom", state.cropbottom)
409 cropper.set_property("right", state.cropright)
410
411 def selectCompositeModeDefaultSources(self):
412 sectionNames = {
413 CompositeModes.fullscreen: 'fullscreen',
414 CompositeModes.side_by_side_equal: 'side-by-side-equal',
415 CompositeModes.side_by_side_preview: 'side-by-side-preview',
416 CompositeModes.picture_in_picture: 'picture-in-picture'
417 }
418
419 compositeModeName = self.compositeMode.name
420 sectionName = sectionNames[self.compositeMode]
421
422 try:
423 defSource = Config.get(sectionName, 'default-a')
424 self.setVideoSourceA(self.names.index(defSource))
425 self.log.info('Changing sourceA to default of Mode %s: %s',
426 compositeModeName, defSource)
427 except Exception as e:
428 pass
429
430 try:
431 defSource = Config.get(sectionName, 'default-b')
432 self.setVideoSourceB(self.names.index(defSource))
433 self.log.info('Changing sourceB to default of Mode %s: %s',
434 compositeModeName, defSource)
435 except Exception as e:
436 pass
437
438 def on_handoff(self, object, buffer):
439 if self.padStateDirty:
440 self.padStateDirty = False
441 self.log.debug('[Streaming-Thread]: Pad-State is Dirty, '
442 'applying new Mixer-State')
443 self.applyMixerState()
444
445 def on_eos(self, bus, message):
446 self.log.debug('Received End-of-Stream-Signal on Mixing-Pipeline')
447
448 def on_error(self, bus, message):
449 self.log.debug('Received Error-Signal on Mixing-Pipeline')
450 (error, debug) = message.parse_error()
451 self.log.debug('Error-Details: #%u: %s', error.code, debug)
266 # apply new scene (hard cut)
267 self.log.debug(
268 "setting composite '%s' to scene", newComposite.name)
269 self.scene.set(targetA, newComposite.Az(below))
270 self.scene.set(targetB, newComposite.Bz(above))
271 # make all other sources invisible
272 for source in self.sources:
273 if source not in [targetA, targetB]:
274 self.log.debug("making source %s invisible", source)
275 self.scene.set(source, Frame(True, alpha=0, zorder=-1))
276
277 # get current and new background source by the composites
278 curBgSource = Config.getBackgroundSource(curCompositeName)
279 newBgSource = Config.getBackgroundSource(newCompositeName)
280 if curBgSource != newBgSource:
281 # found transition?
282 if transition:
283 # apply found transition
284 self.log.debug("committing background fading to scene")
285 # keep showing old background at z-order 0
286 curBgFrame = Frame(True, zorder=0, rect=[0,0,*Config.getVideoResolution()])
287 self.bgScene.set(curBgSource, curBgFrame)
288 # fade new background in at z-order 1 so it will cover the old one at end
289 newBgFrame = Frame(True, alpha=0, zorder=1, rect=[0,0,*Config.getVideoResolution()])
290 self.bgScene.commit(newBgSource, fade_alpha(newBgFrame,255,transition.frames()))
291 else:
292 # apply new scene (hard cut)
293 self.log.debug(
294 "setting new background to scene")
295 # just switch to new background
296 bgFrame = Frame(True, zorder=0, rect=[0,0,*Config.getVideoResolution()])
297 self.bgScene.set(newBgSource, bgFrame)
298 # make all other background sources invisible
299 for source in self.bgSources:
300 if source not in [curBgSource,newBgSource]:
301 self.log.debug("making background source %s invisible", source)
302 self.bgScene.set(source, Frame(True, alpha=0, zorder=-1))
303 else:
304 # report unknown elements of the target scene
305 if not newComposite:
306 self.log.error("Unknown composite '%s'", newCompositeName)
307 if not newA in self.sources:
308 self.log.error("Unknown source '%s'", newA)
309 if not newB in self.sources:
310 self.log.error("Unknown source '%s'", newB)
311
312 # remember scene we've set
313 self.compositeMode = newComposite.name
314 self.sourceA = newA
315 self.sourceB = newB
316
317 def setComposite(self, command, useTransitions=False):
318 ''' parse switch to the composite described by string command '''
319 # expect string as parameter
320 assert type(command) == str
321 # parse command
322 command = CompositeCommand.from_str(command)
323 self.log.debug("Setting new composite by string '%s'", command)
324 self.setCompositeEx(command.composite, command.A,
325 command.B, useTransitions)
326
327 def testCut(self, command):
328 # expect string as parameter
329 assert type(command) == str
330 # parse command
331 command = CompositeCommand.from_str(command)
332 if (command.composite != self.compositeMode or command.A != self.sourceA or command.B != self.sourceB):
333 return command.A, command.B
334 else:
335 return False
336
337 def testTransition(self, command):
338 # expect string as parameter
339 assert type(command) == str
340 # parse command
341 command = CompositeCommand.from_str(command)
342 self.log.debug("Testing if transition is available to '%s'", command)
343 return self.setCompositeEx(command.composite, command.A,
344 command.B, True, True)
345
346 def getVideoSources(self):
347 ''' legacy command '''
348 return [self.sourceA, self.sourceB]
452349
453350 def setVideoSourceA(self, source):
454 # swap if required
455 if self.sourceB == source:
456 self.sourceB = self.sourceA
457
458 self.sourceA = source
459 self.recalculateMixerState()
351 ''' legacy command '''
352 self.setCompositeEx(None, source, None, useTransitions=False)
460353
461354 def getVideoSourceA(self):
355 ''' legacy command '''
462356 return self.sourceA
463357
464358 def setVideoSourceB(self, source):
465 # swap if required
466 if self.sourceA == source:
467 self.sourceA = self.sourceB
468
469 self.sourceB = source
470 self.recalculateMixerState()
359 ''' legacy command '''
360 self.setCompositeEx(None, None, source, useTransitions=False)
471361
472362 def getVideoSourceB(self):
363 ''' legacy command '''
473364 return self.sourceB
474365
475 def setCompositeMode(self, mode, apply_default_source=True):
476 self.compositeMode = mode
477
478 if apply_default_source:
479 self.selectCompositeModeDefaultSources()
480
481 self.recalculateMixerState()
366 def setCompositeMode(self, mode):
367 ''' legacy command '''
368 self.setCompositeEx(mode, None, None, useTransitions=False)
482369
483370 def getCompositeMode(self):
371 ''' legacy command '''
484372 return self.compositeMode
373
374 def getComposite(self):
375 ''' legacy command '''
376 return str(CompositeCommand(self.compositeMode, self.sourceA, self.sourceB))
377
378 def setOverlay(self, location):
379 ''' set up overlay file by location '''
380 self.overlay.set(location)
381
382 def showOverlay(self, visible):
383 ''' set overlay visibility '''
384 self.overlay.show(visible, self.getPlayTime())
385
386 def getOverlay(self):
387 ''' get current overlay file location '''
388 return self.overlay.get()
389
390 def getOverlayVisible(self):
391 ''' get overlay visibility '''
392 return self.overlay.visible()
0 #!/usr/bin/env python3
1 from configparser import SafeConfigParser
2 from vocto.transitions import Composites, Transitions, L, T, R, B, X, Y
3 from PIL import Image, ImageDraw, ImageFont
4 # for integer maximum size
5 import sys
6 # for calling convert to generate animated GIF
7 from subprocess import call
8 import copy
9 import re
10 import logging
11 import argparse
12
13
14 def read_arguments():
15 global Args
16 # read arguments
17 __all__ = ['Args']
18 parser = argparse.ArgumentParser(
19 description='transition - tool to generate voctomix transition animations for testing')
20 parser.add_argument('composite', nargs='*',
21 help="list of composites to generate transitions between (use all available if not given)")
22 parser.add_argument('-f', '--config', action='store', default="voctocore/default-config.ini",
23 help="name of the configuration file to load")
24 parser.add_argument('-m', '--map', action='count',
25 help="print transition table")
26 parser.add_argument('-l', '--list', action='count',
27 help="list available composites")
28 parser.add_argument('-g', '--generate', action='count',
29 help="generate animation")
30 parser.add_argument('-t', '--title', action='count', default=0,
31 help="draw composite names and frame count")
32 parser.add_argument('-k', '--keys', action='count', default=0,
33 help="draw key frames")
34 parser.add_argument('-c', '--corners', action='count', default=0,
35 help="draw calculated interpolation corners")
36 parser.add_argument('-C', '--cross', action='count', default=0,
37 help="draw image cross through center")
38 parser.add_argument('-r', '--crop', action='count', default=0,
39 help="draw image cropping border")
40 parser.add_argument('-n', '--number', action='count',
41 help="when using -g: use consecutively numbers as file names")
42 parser.add_argument('-P', '--nopng', action='count',
43 help="when using -g: do not write PNG files (forces -G)")
44 parser.add_argument('-L', '--leave', action='count',
45 help="when using -g: do not delete temporary PNG files")
46 parser.add_argument('-G', '--nogif', action='count',
47 help="when using -g: do not generate animated GIFS")
48 parser.add_argument('-v', '--verbose', action='count', default=0,
49 help="also print WARNING (-v), INFO (-vv) and DEBUG (-vvv) messages")
50 parser.add_argument('-s', '--size', action='store',
51 help="set frame size 'WxH' W and H must be pixels")
52 parser.add_argument('-S', '--fps', '--speed', action='store', default=25,
53 help="animation resolution (frames per second)")
54 Args = parser.parse_args()
55 # implicit options
56 if Args.nopng:
57 Args.nogif = 1
58
59
60 def init_log():
61 global Args, log
62 # set up logging
63 FORMAT = '%(message)s'
64 logging.basicConfig(format=FORMAT)
65 logging.root.setLevel([logging.ERROR, logging.WARNING,
66 logging.INFO, logging.DEBUG][Args.verbose])
67 log = logging.getLogger('Transitions Test')
68
69
70 def read_config(filename=None):
71 global log, Args
72 if not filename:
73 filename = Args.config
74 # load INI files
75 config = SafeConfigParser()
76 config.read(filename)
77 if Args.size:
78 r = re.match(
79 r'^\s*(\d+)\s*x\s*(\d+)\s*$', Args.size)
80 else:
81 r = re.match(
82 r'^.*width\s*=\s*(\d+).*height\s*=\s*(\d+).*$', config.get('mix', 'videocaps'))
83 if r:
84 size = [int(r.group(1)), int(r.group(2))]
85 # read frames per second
86 if Args.fps:
87 fps = int(Args.fps)
88 else:
89 r = re.match(
90 r'^\s*framerate\s*=\s*(\d+)/(\d+).*$', config.get('mix', 'videocaps'))
91 if r:
92 fps = float(r.group(1)) / float(r.group(2))
93 print (size, fps)
94 # read composites from configuration
95 log.info("reading composites from configuration...")
96 composites = Composites.configure(config.items('composites'), size)
97 log.debug("read %d composites:\n\t%s\t" %
98 (len(composites), '\n\t'.join(sorted(composites))))
99 # maybe overwirte targets by arguments
100 if Args.composite:
101 # check for composites in arguments
102 targets = [composites[c] for c in set(Args.composite)]
103 else:
104 # list of all relevant composites we like to target
105 targets = Composites.targets(composites)
106 intermediates = Composites.intermediates(composites)
107 # list targets and itermediates
108 if Args.list:
109 print("%d targetable composite(s):\n\t%s\t" %
110 (len(targets), '\n\t'.join([t.name for t in targets])))
111 print("%d intermediate composite(s):\n\t%s\t" %
112 (len(intermediates), '\n\t'.join([t.name for t in intermediates])))
113 # read transitions from configuration
114 log.info("reading transitions from configuration...")
115 transitions = Transitions.configure(
116 config.items('transitions'), composites, targets, fps)
117 log.info("read %d transition(s)" % transitions.count())
118 if Args.map:
119 print("transition table:\n%s" % transitions)
120 # maybe overwirte targets by arguments
121 if Args.composite:
122 # check for composites in arguments
123 sequence = Args.composite
124 else:
125 # generate sequence of targets
126 sequence = Transitions.travel([t.name for t in targets])
127 log.debug("using %d target composite(s):\n\t%s\t" %
128 (len(targets), '\n\t'.join([t.name for t in targets])))
129
130 # return config
131 return size, fps, sequence, transitions, composites
132
133
134 def draw_text(draw, size, line_or_pos, text, fill=(255, 255, 255, 255), align=0):
135 # get a font
136 font = ImageFont.truetype("FreeSans.ttf",
137 11 if size[X] < 400 else
138 (13 if size[X] < 800 else
139 20))
140 if type(line_or_pos) == int:
141 assert not align
142 line_factor = 1.3
143 line_height = font.getsize("|")[Y] * line_factor
144 # measure text size
145 x = (size[X] - draw.textsize(text, font=font)[X]) / 2
146 if line_or_pos >= 0:
147 y = line_height * (line_or_pos - 1) + line_height * \
148 (line_factor - 1.0)
149 else:
150 y = size[Y] + line_height * line_or_pos
151 else:
152 assert type(line_or_pos) == list
153 x, y = line_or_pos
154 if align == 0:
155 x = (x - draw.textsize(text, font=font)[X]) / 2
156 elif align == -1:
157 x = x - draw.textsize(text, font=font)[X]
158 y = y - draw.textsize(text, font=font)[Y]
159 draw.text([x, y], text, font=font, fill=fill)
160
161
162 def draw_composite(size, composite, swap=False):
163 # indices in size and tsize
164 X, Y = 0, 1
165 # create an image to draw into
166 imageBg = Image.new('RGBA', size, (40, 40, 40, 255))
167 imageA = Image.new('RGBA', size, (0, 0, 0, 0))
168 imageB = Image.new('RGBA', size, (0, 0, 0, 0))
169 imageFg = Image.new('RGBA', size, (0, 0, 0, 0))
170 # create a drawing context
171 drawBg = ImageDraw.Draw(imageBg)
172 drawA = ImageDraw.Draw(imageA)
173 drawB = ImageDraw.Draw(imageB)
174 drawFg = ImageDraw.Draw(imageFg)
175 if Args.cross:
176 # mark center lines
177 drawFg.line((size[X] / 2, 0, size[X] / 2, size[Y]),
178 fill=(0, 0, 0, 128))
179 drawFg.line((0, size[Y] / 2, size[X], size[Y] / 2),
180 fill=(0, 0, 0, 128))
181
182 # simulate swapping sources
183 a, b = composite.A(), composite.B()
184 if swap:
185 a, b = b, a
186 if Args.title:
187 draw_text(drawFg, size, 2, "(swapped sources)")
188
189 if Args.crop:
190 # draw source frame
191 drawA.rectangle(a.rect, outline=(128, 0, 0, a.alpha))
192 drawB.rectangle(b.rect, outline=(0, 0, 128, b.alpha))
193 # draw cropped source frame
194 drawA.rectangle(a.cropped(), fill=(128, 0, 0, a.alpha))
195 drawB.rectangle(b.cropped(), fill=(0, 0, 128, b.alpha))
196
197 # silly way to draw on RGBA frame buffer, hey - it's python
198 return Image.alpha_composite(
199 Image.alpha_composite(
200 Image.alpha_composite(imageBg, imageA), imageB), imageFg)
201
202
203 def draw_transition(size, transition, info=None):
204 # animation as a list of images
205 images = []
206 # render all frames
207 for i in range(transition.frames()):
208 # create an image to draw into
209 imageBg = draw_composite(size, transition.composites[i],
210 transition.flip is not None and i >= transition.flip)
211 imageDesc = Image.new('RGBA', size, (0, 0, 0, 0))
212 imageFg = Image.new('RGBA', size, (0, 0, 0, 0))
213 # create a drawing context
214 drawBg = ImageDraw.Draw(imageBg)
215 drawDesc = ImageDraw.Draw(imageDesc)
216 drawFg = ImageDraw.Draw(imageFg)
217
218 acolor = (256, 128, 128, 128)
219 bcolor = (128, 128, 256, 128)
220
221 if Args.keys:
222 n = 0
223 for key in transition.keys():
224 ac = key.A().rect
225 bc = key.B().rect
226 drawDesc.rectangle(ac, outline=acolor)
227 draw_text(drawDesc, size,
228 [ac[L] + 2, ac[T] + 2],
229 "A.%d" % n, acolor, 1)
230 drawDesc.rectangle(bc, outline=bcolor)
231 draw_text(drawDesc, size,
232 [bc[R] - 2, bc[B] - 2],
233 "B.%d" % n, bcolor, -1)
234 n += 1
235
236 if Args.corners:
237 # draw calculated corner points
238 for n in range(0, i + 1):
239 ar = transition.A(n).rect
240 br = transition.B(n).rect
241 drawDesc.rectangle(
242 (ar[R] - 2, ar[T] - 2, ar[R] + 2, ar[T] + 2), fill=acolor)
243 drawDesc.rectangle(
244 (br[L] - 2, br[T] - 2, br[L] + 2, br[T] + 2), fill=bcolor)
245
246 drawDesc.rectangle(
247 (ar[L] - 1, ar[T] - 1, ar[L] + 1, ar[T] + 1), fill=acolor)
248 drawDesc.rectangle(
249 (ar[L] - 1, ar[B] - 1, ar[L] + 1, ar[B] + 1), fill=acolor)
250 drawDesc.rectangle(
251 (ar[R] - 1, ar[B] - 1, ar[R] + 1, ar[B] + 1), fill=acolor)
252
253 drawDesc.rectangle(
254 (br[R] - 1, br[T] - 1, br[R] + 1, br[T] + 1), fill=bcolor)
255 drawDesc.rectangle(
256 (br[L] - 1, br[B] - 1, br[L] + 1, br[B] + 1), fill=bcolor)
257 drawDesc.rectangle(
258 (br[R] - 1, br[B] - 1, br[R] + 1, br[B] + 1), fill=bcolor)
259
260 if Args.title:
261 draw_text(drawFg, size, -3, transition.name())
262 if not info is None:
263 draw_text(drawFg, size, -2, info)
264 draw_text(
265 drawFg, size, -1, " → ".join([c.name for c in transition.keys()]))
266 draw_text(drawFg, size, 1, "Frame %d" % i)
267 # silly way to draw on RGBA frame buffer, hey - it's python
268 images.append(
269 Image.alpha_composite(
270 Image.alpha_composite(imageBg, imageDesc), imageFg)
271 )
272 # return resulting animation images
273 return images
274
275
276 def save_transition_gif(filename, size, info, transition, time):
277 frames = transition.frames()
278 # save animation
279 log.info("generating transition '%s' (%d ms, %d frames)..." %
280 (transition.name(), int(time), frames))
281 images = draw_transition(size, transition, info)
282 if not Args.nopng:
283 imagenames = []
284 delay = int(time / 10.0 / frames)
285 log.info("saving animation frames into temporary files '%s0000.png'..'%s%04d.png'..." %
286 (filename, filename, transition.frames() - 1))
287 for i in range(0, len(images)):
288 imagenames.append("%s%04d.png" % (filename, i))
289 # save an image
290 images[i].save(imagenames[-1])
291 # generate animated GIF by calling system command 'convert'
292 if not Args.nogif:
293 log.info("creating animated file '%s.gif' with delay %d..." %
294 (filename, delay))
295 call(["convert", "-loop", "0"] +
296 ["-delay", "100"] + imagenames[:1] +
297 ["-delay", "%d" % delay] + imagenames[1:-1] +
298 ["-delay", "100"] + imagenames[-1:] +
299 ["%s.gif" % filename])
300 # delete temporary files?
301 if not Args.leave:
302 log.info("deleting temporary PNG files...")
303 call(["rm"] + imagenames)
304
305
306 def render_composites(size, composites):
307 global log
308 log.debug("rendering composites (%d items):\n\t%s\t" %
309 (len(composites), '\n\t'.join([c.name for c in composites])))
310 for c in composites:
311 if Args.generate:
312 print("saving composite file '%s.png' (%s)..." % (c.name, c.name))
313 draw_composite(size, c).save("%s.png" % c.name)
314
315
316 def render_sequence(size, fps, sequence, transitions, composites):
317 global log
318 log.debug("rendering generated sequence (%d items):\n\t%s\t" %
319 (len(sequence), '\n\t'.join(sequence)))
320
321 # begin at first transition
322 prev_name = sequence[0]
323 prev = composites[prev_name]
324 # cound findings
325 not_found = []
326 found = []
327 # process sequence through all possible transitions
328 for c_name in sequence[1:]:
329 # fetch prev composite
330 c = composites[c_name]
331 # get the right transtion between prev and c
332 log.debug("request transition (%d/%d): %s → %s" %
333 (len(found) + 1, len(sequence) - 1, prev_name, c_name))
334 # actually search for a transitions that does a fade between prev and c
335 transition, swap = transitions.solve(prev, c, prev == c)
336 # count findings
337 if not transition:
338 # report fetched transition
339 log.warning("no transition found for: %s → %s" %
340 (prev_name, c_name))
341 not_found.append("%s -> %s" % (prev_name, c_name))
342 else:
343 # report fetched transition
344 log.debug("transition found: %s\n%s" %
345 (transition.name(), transition))
346 found.append(transition.name())
347 # get sequence frames
348 frames = transition.frames()
349 if Args.generate:
350 filename = ("%03d" % len(
351 found)) if Args.number else "%s_%s" % (prev_name, c_name)
352 print("saving transition animation file '%s.gif' (%s, %d frames)..." %
353 (filename, transition.name(), frames))
354 # generate test images for transtion and save into animated GIF
355 save_transition_gif(filename, size, "%s → %s" % (prev_name, c_name),
356 transition, frames / fps * 1000.0)
357 # remember current transition as next previous
358 prev_name, prev = c_name, c
359 # report findings
360 if found:
361 if Args.list:
362 print("%d transition(s) available:\n\t%s" %
363 (len(found), '\n\t'.join(sorted(found))))
364 if not_found:
365 print("%d transition(s) could NOT be found:\n\t%s" %
366 (len(not_found), "\n\t".join(sorted(not_found))))
367
368 read_arguments()
369 init_log()
370 cfg = read_config()
371 render_composites(cfg[0], Composites.targets(cfg[4]))
372 render_sequence(*cfg)
00 #!/usr/bin/env python3
11 import gi
2 import sdnotify
23 import signal
34 import logging
45 import sys
56
7 sys.path.insert(0, '.')
8 from vocto.debug import gst_log_messages
9
610 # import GStreamer and GLib-Helper classes
711 gi.require_version('Gst', '1.0')
812 gi.require_version('GstNet', '1.0')
9 from gi.repository import Gst, GObject
13 from gi.repository import Gst, GLib
1014
1115 # import local classes
1216 from lib.loghandler import LogHandler
2529 'is too old, at least', minPy, 'is required')
2630
2731
28 # init GObject & Co. before importing local classes
29 GObject.threads_init()
30
31
3232 # main class
3333 class Voctocore(object):
3434
4040 from lib.controlserver import ControlServer
4141
4242 self.log = logging.getLogger('Voctocore')
43 self.log.debug('creating GObject-MainLoop')
44 self.mainloop = GObject.MainLoop()
43 self.log.debug('Creating GLib-MainLoop')
44 self.mainloop = GLib.MainLoop()
4545
4646 # initialize subsystem
47 self.log.debug('creating A/V-Pipeline')
47 self.log.debug('Creating A/V-Pipeline')
4848 self.pipeline = Pipeline()
4949
50 self.log.debug('creating ControlServer')
50 self.log.debug('Creating ControlServer')
5151 self.controlserver = ControlServer(self.pipeline)
5252
5353 def run(self):
54 self.log.info('running GObject-MainLoop')
54 self.log.info('Running. Waiting for connections....')
5555 try:
5656 self.mainloop.run()
5757 except KeyboardInterrupt:
5858 self.log.info('Terminated via Ctrl-C')
5959
6060 def quit(self):
61 self.log.info('quitting GObject-MainLoop')
61 self.log.info('Quitting.')
6262 self.mainloop.quit()
6363
6464
7575 handler = LogHandler(docolor, Args.timestamp)
7676 logging.root.addHandler(handler)
7777
78 if Args.verbose >= 2:
79 level = logging.DEBUG
80 elif Args.verbose == 1:
81 level = logging.INFO
82 else:
83 level = logging.WARNING
78 levels = { 3 : logging.DEBUG, 2 : logging.INFO, 1 : logging.WARNING, 0 : logging.ERROR }
79 logging.root.setLevel(levels[Args.verbose])
8480
85 logging.root.setLevel(level)
81 gst_levels = { 3 : Gst.DebugLevel.DEBUG, 2 : Gst.DebugLevel.INFO, 1 : Gst.DebugLevel.WARNING, 0 : Gst.DebugLevel.ERROR }
82 gst_log_messages(gst_levels[Args.gstreamer_log])
8683
8784 # make killable by ctrl-c
8885 logging.debug('setting SIGINT handler')
9996 logging.debug('initializing Voctocore')
10097 voctocore = Voctocore()
10198
99 # Inform systemd that we are ready
100 # for use with the notify service type
101 n = sdnotify.SystemdNotifier()
102 n.notify("READY=1")
103
102104 logging.debug('running Voctocore')
103105 voctocore.run()
104106
0 # Voctogui - The GUI frontend for Voctocore
1
2 ![Screenshot of voctogui in action](voctomix.png)
3
4 ## Keyboard Shortcuts
5 ### Composition Modes
6 - `F1` Fullscreen
7 - `F2` Picture in Picture
8 - `F3` Side-by-Side Equal
9 - `F4` Side-by-Side Preview
10
11 ### Select A-Source
12 - `1` Source Nr. 1
13 - `2` Source Nr. 2
14 - …
15
16 ### Select B-Source
17 - `Ctrl+1` Source Nr. 1
18 - `Ctrl+2` Source Nr. 2
19 - …
20
21 ### Set Fullscteen
22 - `Alt+1` Source Nr. 1
23 - `Alt+2` Source Nr. 2
24 - …
25
26 ### Stream Blanking
27 - F11 Set stream to Pause-Loop
28 - F12 Set stream Live
29
30 ### Other options
31 - `t` Cut
32
33 ### Select an Audio-Source
34 Click twice on the selection combobox, then select your source within 5 Seconds. (It will auto-lock again after 5 seconds.)
35
36 ## Configuration
37 On startup the GUI reads the following configuration files:
38 - `<install-dir>/default-config.ini`
39 - `<install-dir>/config.ini`
40 - `/etc/voctomix/voctogui.ini`
41 - `/etc/voctogui.ini`
42 - `<homedir>/.voctogui.ini`
43 - `<File specified on Command-Line via --ini-file>`
44
45 From top to bottom the individual settings override previous settings. `default-config.ini` should not be edited, because a missing setting will result in a Python exception.
46
47 On startup the GUI fetches all configuration settings from the core and merges them into the GUI config.
0 # 1. VOC2GUI
1
2 The GUI new front-end for **VOC2CORE**.
3
4 ## 1.1. Contents
5
6 <!-- TOC -->
7
8 - [1.1. Contents](#11-contents)
9 - [1.2. Purpose](#12-purpose)
10 - [1.3. Installation](#13-installation)
11 - [1.4. Running](#14-running)
12 - [1.4.1. Usage](#141-usage)
13 - [1.4.2. Options](#142-options)
14 - [1.5. Configuration](#15-configuration)
15 - [1.5.1. Connecting to Host](#151-connecting-to-host)
16 - [1.5.2. Server Configuration Overwrites Client's One](#152-server-configuration-overwrites-clients-one)
17 - [1.5.3. Main Window Properties](#153-main-window-properties)
18 - [1.5.3.1. Window Size](#1531-window-size)
19 - [1.5.3.2. Force Full Screen](#1532-force-full-screen)
20 - [1.5.3.3. Main Window Toolbar](#1533-main-window-toolbar)
21 - [1.5.3.4. Hide Close Button](#1534-hide-close-button)
22 - [1.5.3.5. Show Full Screen Button](#1535-show-full-screen-button)
23 - [1.5.3.6. Show Expert Buttons](#1536-show-expert-buttons)
24 - [1.5.4. Mixing Toolbars](#154-mixing-toolbars)
25 - [1.5.4.1. Common Widget and Button Configuration](#1541-common-widget-and-button-configuration)
26 - [1.5.4.2. Sources Toolbars](#1542-sources-toolbars)
27 - [1.5.4.3. Composites](#1543-composites)
28 - [1.5.4.4. Modify Toolbar](#1544-modify-toolbar)
29 - [1.5.4.5. Mix Toolbar](#1545-mix-toolbar)
30 - [1.5.4.6. Insertions Toolbar](#1546-insertions-toolbar)
31 - [1.5.5. Customize UI Style](#155-customize-ui-style)
32 - [1.6. Usage](#16-usage)
33
34 <!-- /TOC -->
35
36 ## 1.2. Purpose
37
38 The **VOC2GUI** is a GUI client for **VOC2CORE**.
39 It can connect to a running **VOC2CORE** host and then uses *GTK* to provide a graphical interface to control a lot of the core's functionality.
40
41 ![**VOC2GUI** Screenshot](doc/images/voc2gui.png)
42
43 Since the focus of VOC2MIX is to process and control the video mixing of a single A/V recorded talk on stage, **VOC2GUI** is the part where A/V mixing operators can get an easy to use interface to do their job while the event is running.
44
45 This include mixing video, audio, blinding of live output and monitoring the VOC2MIX setup.
46
47 After reading a configuration **VOC2GUI** connect to a host that is doing the bare work of mixing several input video and audio sources into several mixing outputs.
48 All A/V processing of the core is done by an underlying *GStreamer* instance that processes a so-called pipeline.
49 To understand what you are able to control with **VOC2GUI** you might first try to understand the principle pipeline construct of **VOC2CORE**.
50 In this document we presume that you know what your core was configured for.
51
52 ## 1.3. Installation
53
54 Todo.
55
56 ## 1.4. Running
57
58 ### 1.4.1. Usage
59
60 ```text
61 python3 voctogui.py [-h] [-v] [-c {auto,always,never}] [-t] [-i INI_FILE] [-H HOST] [-d] [-D GST_DEBUG_DETAILS] [-g]
62 ```
63
64 ### 1.4.2. Options
65
66 | Option | Alternative | Description
67 | ------ | ----- | -----
68 | `-h` | `--help` | show this help message and exit
69 | `-v` | `--verbose` | Set verbosity level by using `-v`, `-vv` or `-vvv`.
70 | `-c` { `auto`, `always`, `never` } | `--color` { `auto`, `always`, `never` } | Control the use of colors in the Log-Output.<br>Default: `auto`
71 | `-t` | `--timestamp` | Enable timestamps in the Log-Output
72 | `-i` *INI_FILE* | `--ini-file` *INI_FILE* | Load a custom configuration file *INI_FILE*.<br>Default: `./voctogui/default-config.ini`
73 | `-H` *HOST* | `--host` *HOST* | Connect to this host instead of the configured one.
74 | `-d` | `--dot` | Generate *DOT* graph files of pipelines into directory set by environment variable `GST_DEBUG_DUMP_DOT_DIR`
75 | `-D` *GST_DEBUG_DETAILS* | `--gst-debug-details` *GST_DEBUG_DETAILS* | Like `-d` but set detail level in *DOT* graph. *GST_DEBUG_DETAILS* must be a combination the following values:<br>`1` = show caps-name on edges<br>`2` = show caps-details on edges<br>`4` = show modified parameters on elements<br>`8` = show element states<br>`16` = show full element parameter values even if they are very long<br>Default: `15` = show all the typical details that one might want (`15=1+2+4+8`)
76 | `-g` | `--gstreamer-log` | Log gstreamer messages into voctocore log. <br>Set log level by using `-g`, `-gg` or `-ggg`).
77
78 ## 1.5. Configuration
79
80 By default **VOC2GUI** reads a configuration file from it's repository called `./voctogui/default-config.ini` but it also asks the core for it's configuration.
81
82 ### 1.5.1. Connecting to Host
83
84 Do be functional **VOC2GUI** has to connext to the a **VOC2CORE** instance via *TCP/IP*.
85 Usually this is done by setting the attribtue `server` / `host` within the configuration.
86
87 ```ini
88 [server]
89 host=localhost
90 ```
91
92 The `host` entry in the configuration is obligatory unsless you use `-H` to set the host.
93 The argument overrides the configuration if you use both at the same time.
94
95 ### 1.5.2. Server Configuration Overwrites Client's One
96
97 The attributes in the core configuration overwrite the ones defined in the GUI's one so every item (except `server/host`) does not have to be included within the GUI configuration but can be defined by the core configuration to unify all connecting GUI instances.
98 Instead you could leave things more open to set them in the client configuration individually.
99
100 ### 1.5.3. Main Window Properties
101
102 Because **VOC2GUI** is a *GTK* application it needs to open a main window on the screen.
103 You can influence the way this window appears by the following options:
104
105 ```ini
106 [mainwindow]
107 width=1920
108 height=1080
109 forcefullscreen=true
110 ```
111
112 #### 1.5.3.1. Window Size
113
114 Add pixel sizes to `width` and `height` to set the main window extents when not in full screen mode.
115
116 #### 1.5.3.2. Force Full Screen
117
118 Set `forcefullscreen` if you like **VOC2GUI** to start in full screen mode.
119
120 #### 1.5.3.3. Main Window Toolbar
121
122 The availability of the buttons in the main window toolbar can be customized by the following options:
123
124 ```ini
125 [toolbar]
126 close=true
127 fullscreen=true
128 queues=true
129 ports=true
130 ```
131
132 #### 1.5.3.4. Hide Close Button
133
134 Setting `close` to false will hide the close button which is on by default.
135
136 #### 1.5.3.5. Show Full Screen Button
137
138 By setting `fullscreen` to `true` a button wilöl appear which allows the user to toggle full screen mode.
139 Some window managers might still give the user the ability to toogle full screen mode even if this button is hidden.
140
141 #### 1.5.3.6. Show Expert Buttons
142
143 Setting `queues` and `ports` to `true` will show buttons which toggle the visiblity of the queues and the ports bar.
144
145 **Hint**: If you want to prevent the users to enable those features by changing the GUI configuration file you have to set it to `false` wthin the core configuration!
146
147 ### 1.5.4. Mixing Toolbars
148
149 **VOC2GUI** can show several tool bars enveloping different aspects of the mixing process.
150 In the following paragraphs you learn how to configure them to customize availability of different functionalities, rename widgets, change keyboard accelerators and more.
151
152 #### 1.5.4.1. Common Widget and Button Configuration
153
154 Every mixing toolbar can be configured by using the following attributes.
155
156 | Attribute | Description
157 | ----------- | --------------------------------------------
158 | `.key` | Set the keyboard code that triggers this button.<br>See <https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html> about accelerator key codes in GTK.
159 | `.name` | Customize name of the button on screen.
160 | `.tip` | Customize the tool top of the button on screen.
161 | `.expand` | Whether to expand the button within GTK layout.
162 | `.pos` | Position (0..n) within the toolbar.<br>If not given append in order they appear in configuration.
163 | `.replace` | Add composite modificator by character replacement (see chapter *Modifiers* below)
164
165 #### 1.5.4.2. Sources Toolbars
166
167 Without configuring the sources toolbars explicitly **VOC2GUI** lists automatically the available A/B sources as buttons and names them in upper case letters.
168 Tooltips will be generated too and keyboard accelerators will be set to `F1`..`F4` for sources A and `1`..`4` for sources B.
169 If you want to vary the visual appearance of the buttons you can use the two sections `toolbar.sources.a` and `toolbar.sources.b`:
170
171 ```ini
172 [toolbar.sources.a]
173 buttons = cam,grabber
174
175 cam.name = CAMERA
176 cam.tip = Select camera on A
177
178 grabber.name = LAPTOP
179 grabber.tip = "Select speaker's laptop on A"
180
181 [toolbar.sources.b]
182 cam.name = CAMERA
183 cam.tip = Select camera on B
184
185 grabber.name = LAPTOP
186 grabber.tip = "Select speaker's laptop on B"
187 ```
188
189 First you list all sources which shall be shown buttons for by using the `buttons` attribute followed by a comma separated list of the related source's names.
190 These sources must be defined within the host's configuration.
191
192 Then you might add one or more of the common attributes.
193
194 #### 1.5.4.3. Composites
195
196 Without any configuration composite buttons will be named automatically by their uppercased source names and keyboard accelerators will be `F5`..`F8`.
197
198 If you want to vary the visual appearance of the buttons you can use the two sections `toolbar.composites`:
199
200 ```ini
201 [toolbar.composites]
202 buttons = fullscreen, sidebyside
203
204 fullscreen.name = FULL SCREEN
205 fullscreen.tip = Show channel A on full screen
206
207 sidebyside.name = SIDE BY SIDE
208 sidebyside.tip = Put channel A beside channel B
209 ```
210
211 These composites must be defined within the host's configuration.
212
213 #### 1.5.4.4. Modify Toolbar
214
215 Without any `.key` attributes keyboard accelerators of the buttons in `toolbar.mods` will be `F9`..`F12`.
216 Because there are no default modifiers you are free to define some:
217
218 ```ini
219 [toolbar.mods]
220 buttons = mirror,ratio
221
222 mirror.name = MIRROR
223 mirror.replace = lecture->|lecture
224
225 ratio.name = 4:3
226 ratio.replace = lecture->lecture_43
227 ```
228
229 Currently there is only one possibility to add modifiers by using the attribute `replace`:
230
231 ##### Replacement Modifier
232
233 Composites are described in **VOCTOMIX** by a specific string format `c(A,B)`, where `c` is the composite and `A` and `B` the sources to show).
234 For example if you have two sources `cam` and `grabber` and a composite `lecture`.
235 Assume that `lecture` is a composite which shows a big picture of source A and beside a small one of source B.
236 You can user modifications for this composite by using *mirroring* or just a fancy way to name your composites.
237
238 *Mirroring* is done by adding a `|` in front of the composites name which then mirrors all coordinates of the composite.
239 So if `lecture` has the big picture at the left and the other on the right then `|lecture` as the big picture at the right and the other at the left.
240 Usually this is helpfull if the big picture represents a laptop screen showing a presentation and the small one a camera picture of the speaker.
241 If the speaker walks from left to right you might use *mirroring* to fix the visual impression, when the speaker is not looking at the presentation.
242
243 Another way is to have two different composites like one for when the laptop screen can have different ratios (like 16:9 and 4:3).
244 You then can configure an additional lecture composite called `lecture_43` which crops the laptop screen content to 4:3 for example.
245
246 In both cases we can define modifier buttons with `.replace` just by defining a replacement rule for the composite definition string that replaces `lecture` with `|lecture` or `lecture` with `lecture_43` and vice versa like configured in the above sample.
247
248 #### 1.5.4.5. Mix Toolbar
249
250 The mix toolbar can show the following buttons `retake`, `cut`, `trans`.
251 By adding the attribute `buttons` to the section `toolbar.mix` you might restrict the availability of those.
252
253 Without any `.key` attributes keyboard accelerators of the buttons in `toolbar.mods` will be `BackSpace` for `retake`, `Return` for `cut` and `space` for `trans`.
254
255 ```ini
256 [toolbar.mix]
257 buttons = cut,trans
258
259 trans.expand = true
260 ```
261
262 The example will hide the retake button and the transition button will expand in the window layout so that it might be bigger than the cut button.
263
264 #### 1.5.4.6. Insertions Toolbar
265
266 Todo.
267
268 ```ini
269 [toolbar.insert]
270 auto-off.key = o
271 auto-off.tip = automatically turn off insertion before every mix
272
273 update.key = u
274 update.tip = Update current event
275
276 insert.key = i
277 insert.tip = Show or hide current insertion
278 ```
279
280 ### 1.5.5. Customize UI Style
281
282 Edit `voctogui/ui/voctogui.css` to change the way the user interface will be drawn.
283 Currently there is no list of class names or IDs but you might start **VOC2GUI** with *GTK* debugging to get these information by setting environment variable `GTK_DEBUG` to `interactive`:
284
285 ```bash
286 GTK_DEBUG=interactive voctogui/voctogui.py
287 ```
288
289 You will get a window which allows you to browse through all widgets within the **VOC2GUI** window.
290
291 ## 1.6. Usage
292
293 Todo.
00 [server]
11 host=localhost
2 #host=10.73.23.3
23
34 [previews]
45 width=320
56 ;height=180
7 ;nameoverlay=false
68
79 ; true = use if the server provides it
810 ; false = never use it
911 use=true
1012
11 [mainvideo]
12 # disabled by default because it seems there's an issue, see
13 # https://github.com/voc/voctomix/issues/37
14 # number of vu meters to display, set vumeter=all to to show VU meters of all audiostreams in gui
15 vumeter=1
16 playaudio=false
13 [videodisplay]
14 # Use VAAPI's renderer, requires VAAPI-based previews
15 #system=vaapi
1716
18 [videodisplay]
1917 # Use OpenGL - most performant
2018 #system=gl
2119
2523 # Use simple X-Images - least performant
2624 system=x
2725
28 [misc]
26 [toolbar]
2927 close=true
30 cut=true
28 ports=true
29 queues=true
30 fullscreen=true
3131
3232 [audio]
3333 ; Show volume controls even if a default audio source is set
3434 ;forcevolumecontrol=true
35 ; play audio by default
36 play=false
3537
3638 [mainwindow]
3739 # resize main window
3941 ;height=1000
4042 # force main window to stay full screen
4143 ;forcefullscreen=true
44 vumeter=all
0 import sys
1 sys.path.insert(0, '..')
2 sys.path.insert(0, '.')
99
1010 parser = argparse.ArgumentParser(description='Voctogui')
1111 parser.add_argument('-v', '--verbose', action='count', default=0,
12 help="Also print INFO and DEBUG messages.")
12 help="Set verbosity level by using -v, -vv or -vvv.")
1313
1414 parser.add_argument('-c', '--color',
1515 action='store',
2121 help="Enable timestamps in the Log-Output")
2222
2323 parser.add_argument('-i', '--ini-file', action='store',
24 help="Load a custom config.ini-File")
25
26 parser.add_argument('-u', '--ui-file', action='store',
27 help="Load a custom .ui-File")
24 help="Load a custom configuration file")
2825
2926 parser.add_argument('-H', '--host', action='store',
3027 help="Connect to this host "
3128 "instead of the configured one.")
3229
30 parser.add_argument('-d', '--dot', action='store_true',
31 help="Generate DOT files of pipelines into directory given in environment variable GST_DEBUG_DUMP_DOT_DIR")
32
33 parser.add_argument('-D', '--gst-debug-details', action='store', default=15,
34 help="Set details in dot graph. GST_DEBUG_DETAILS must be a combination the following values: 1 = show caps-name on edges, 2 = show caps-details on edges, 4 = show modified parameters on elements, 8 = show element states, 16 = show full element parameter values even if they are very long. Default: 15 = show all the typical details that one might want (15=1+2+4+8)")
35
36 parser.add_argument('-g', '--gstreamer-log', action='count', default=0,
37 help="Log gstreamer messages into voctocore log (Set log level by using -g, -gg or -ggg).")
38
3339 Args = parser.parse_args()
0 #!/usr/bin/env python3
1 import logging
2 import os
3 import json
4 import math
5 from configparser import NoOptionError
6
7 from gi.repository import Gtk, Gdk, GObject
8 import lib.connection as Connection
9
10 from lib.config import Config
11 from vocto.port import Port
12
13 class AudioDisplay(object):
14
15 def __init__(self, audio_box, source, uibuilder, has_volume=True):
16 self.log = logging.getLogger('VideoPreviewsController')
17 self.source = source
18 self.panel = None
19 self.panels = dict()
20 self.audio_streams = None
21 self.volume_sliders = {}
22 if source in Config.getSources():
23 self.audio_streams = Config.getAudioStreams().get_source_streams(source)
24 for name, stream in self.audio_streams.items():
25 self.panels[name] = self.createAudioPanel(
26 name, audio_box, has_volume, uibuilder)
27 else:
28 self.panel = self.createAudioPanel(source, audio_box, has_volume, uibuilder)
29
30 def createAudioPanel(self, name, audio_box, has_volume, uibuilder):
31 audio = uibuilder.load_check_widget('audio',
32 os.path.dirname(uibuilder.uifile) +
33 "/audio.ui")
34 audio_box.pack_start(audio, fill=False,
35 expand=False, padding=0)
36 audio_label = uibuilder.find_widget_recursive(audio, 'audio_label')
37 audio_label.set_label(name.upper())
38
39 self.init_volume_slider(name, audio, has_volume, uibuilder)
40
41 return {"level": uibuilder.find_widget_recursive(audio, 'audio_level_display')}
42
43 def callback(self, rms, peak, decay):
44 if self.audio_streams:
45 for name, streams in self.audio_streams.items():
46 _rms = [0] * len(streams)
47 _peak = [0] * len(streams)
48 _decay = [0] * len(streams)
49 for stream in streams:
50 _rms[stream.channel] = rms[stream.source_channel]
51 _peak[stream.channel] = peak[stream.source_channel]
52 _decay[stream.channel] = decay[stream.source_channel]
53 self.panels[name]["level"].level_callback(_rms, _peak, _decay)
54 elif self.panel:
55 self.panel["level"].level_callback(rms, peak, decay)
56
57 def init_volume_slider(self, name, audio_box, has_volume, uibuilder):
58 volume_slider = uibuilder.find_widget_recursive(audio_box,
59 'audio_level')
60
61 if has_volume:
62 volume_signal = volume_slider.connect('value-changed',
63 self.slider_changed)
64 volume_slider.set_name(name)
65 volume_slider.add_mark(-20.0, Gtk.PositionType.LEFT, "")
66 volume_slider.add_mark(0.0, Gtk.PositionType.LEFT, "0")
67 volume_slider.add_mark(10.0, Gtk.PositionType.LEFT, "")
68
69 def slider_format(scale, value):
70 if value == -20.0:
71 return "-\u221e\u202fdB"
72 else:
73 return "{:.{}f}\u202fdB".format(value,
74 scale.get_digits())
75 volume_slider.connect('format-value', slider_format)
76 self.volume_sliders[name] = (volume_slider, volume_signal)
77 if not Config.getVolumeControl():
78 volume_slider.set_sensitive(False)
79
80 Connection.on('audio_status', self.on_audio_status)
81 Connection.send('get_audio')
82 else:
83 volume_slider.set_no_show_all(True)
84 volume_slider.hide()
85
86 def slider_changed(self, slider):
87 stream = slider.get_name()
88 value = slider.get_value()
89 volume = 10 ** (value / 20) if value > -20.0 else 0
90 self.log.debug("slider_changed: {}: {:.4f}".format(stream, volume))
91 Connection.send('set_audio_volume {} {:.4f}'.format(stream, volume))
92
93 def on_audio_status(self, *volumes):
94 volumes_json = "".join(volumes)
95 volumes = json.loads(volumes_json)
96
97 for stream, volume in volumes.items():
98 if stream in self.volume_sliders:
99 volume = 20.0 * math.log10(volume) if volume > 0 else -20.0
100 slider, signal = self.volume_sliders[stream]
101 # Temporarily block the 'value-changed' signal,
102 # so we don't (re)trigger it when receiving (our) changes
103 GObject.signal_handler_block(slider, signal)
104 slider.set_value(volume)
105 GObject.signal_handler_unblock(slider, signal)
00 import math
11 import cairo
22
3 from lib.config import Config
4 from gi.repository import Gtk, GLib, Gst
3 from gi.repository import Gtk, GLib
54
65
76 class AudioLevelDisplay(Gtk.DrawingArea):
87 """Displays a Level-Meter of another VideoDisplay into a GtkWidget"""
98 __gtype_name__ = 'AudioLevelDisplay'
109
10 MARGIN = 4
11 CHANNEL_WIDTH = 8
12 LABEL_WIDTH = 20
13
1114 def __init__(self):
12 self.num_audiostreams_ = int(Config.get('mix', 'audiostreams'))
13 meters = Config.get('mainvideo', 'vumeter')
14 if (meters != 'all') and (int(meters) < self.num_audiostreams_):
15 self.num_audiostreams_ = int(meters)
16
17 self.channels = 2
18 acaps = Gst.Caps.from_string(Config.get('mix', 'audiocaps'))
19 self.channels = int(acaps.get_structure(0).get_int("channels")[1])
20
21 self.levelrms = [0] * self.channels * self.num_audiostreams_
22 self.levelpeak = [0] * self.channels * self.num_audiostreams_
23 self.leveldecay = [0] * self.channels * self.num_audiostreams_
15 self.levelrms = []
16 self.levelpeak = []
17 self.leveldecay = []
2418
2519 self.height = -1
26
27 self.set_size_request(20 * self.num_audiostreams_, -1)
2820
2921 # register on_draw handler
3022 self.connect('draw', self.draw_callback)
4941 if channels == 0:
5042 return False
5143
44 yoff = 3
5245 width = self.get_allocated_width()
53 height = self.get_allocated_height()
54
55 # space between the channels in px
56 margin = 2
57
58 # 1 channel -> 0 margins, 2 channels -> 1 margin, 3 channels…
59 channel_width = int((width - (margin * (channels - 1))) / channels)
60
61 # self.log.debug(
62 # 'width: %upx filled with %u channels of each %upx '
63 # 'and %ux margin of %upx',
64 # width, channels, channel_width, channels - 1, margin
65 # )
46 height = self.get_allocated_height()-yoff
6647
6748 # normalize db-value to 0…1 and multiply with the height
6849 rms_px = [self.normalize_db(db) * height for db in self.levelrms]
7758 self.peak_lg = self.gradient(0.75, 0.0, height)
7859 self.decay_lg = self.gradient(1.0, 0.5, height)
7960
61 first_col = True
8062 # draw all level bars for all channels
8163 for channel in range(0, channels):
8264 # start-coordinate for this channel
83 x = (channel * channel_width) + (channel * margin)
65 x = (channel * self.CHANNEL_WIDTH) + (channel * self.MARGIN) + self.LABEL_WIDTH
8466
8567 # draw background
86 cr.rectangle(x, 0, channel_width, height - peak_px[channel])
68 cr.rectangle(x, yoff, self.CHANNEL_WIDTH, height - peak_px[channel])
8769 cr.set_source(self.bg_lg)
8870 cr.fill()
8971
9072 # draw peak bar
9173 cr.rectangle(
92 x, height - peak_px[channel], channel_width, peak_px[channel])
74 x, yoff +height - peak_px[channel], self.CHANNEL_WIDTH, peak_px[channel])
9375 cr.set_source(self.peak_lg)
9476 cr.fill()
9577
9678 # draw rms bar below
9779 cr.rectangle(
98 x, height - rms_px[channel], channel_width,
80 x, yoff + height - rms_px[channel], self.CHANNEL_WIDTH,
9981 rms_px[channel] - peak_px[channel])
10082 cr.set_source(self.rms_lg)
10183 cr.fill()
10284
10385 # draw decay bar
104 cr.rectangle(x, height - decay_px[channel], channel_width, 2)
86 cr.rectangle(x, yoff + height - decay_px[channel], self.CHANNEL_WIDTH, 2)
10587 cr.set_source(self.decay_lg)
10688 cr.fill()
10789
108 # draw medium grey margin bar
109 if margin > 0:
110 cr.rectangle(x + channel_width, 0, margin, height)
90 first_col = False
91
92 # draw db text-markers
93 for db in [-40, -20, -10, -5, -4, -3, -2, -1]:
94 text = str(db)
95 (xbearing, ybearing,
96 textwidth, textheight,
97 xadvance, yadvance) = cr.text_extents(text)
98
99 y = self.normalize_db(db) * height
111100 cr.set_source_rgb(0.5, 0.5, 0.5)
112 cr.fill()
113
114 # draw db text-markers
115 for db in [-40, -20, -10, -5, -4, -3, -2, -1]:
116 text = str(db)
117 (xbearing, ybearing,
118 textwidth, textheight,
119 xadvance, yadvance) = cr.text_extents(text)
120
121 y = self.normalize_db(db) * height
122 if y > peak_px[channels - 1]:
123 cr.set_source_rgb(1, 1, 1)
124 else:
125 cr.set_source_rgb(0, 0, 0)
126 cr.move_to((width - textwidth) - 2, height - y - textheight)
127 cr.show_text(text)
101 cr.move_to(self.LABEL_WIDTH - textwidth - 4, yoff + height - y - textheight)
102 cr.show_text(text)
128103
129104 return True
130105
140115 def clamp(self, value, min_value=0, max_value=1):
141116 return max(min(value, max_value), min_value)
142117
143 def level_callback(self, rms, peak, decay, stream):
144 meter_offset = self.channels * stream
145 for i in range(0, self.channels):
146 self.levelrms[meter_offset + i] = rms[i]
147 self.levelpeak[meter_offset + i] = peak[i]
148 self.leveldecay[meter_offset + i] = decay[i]
149 self.queue_draw()
118 def level_callback(self, rms, peak, decay):
119 if self.levelrms != rms or self.levelpeak != peak \
120 or self.leveldecay != decay:
121 self.levelrms = rms
122 self.levelpeak = peak
123 self.leveldecay = decay
124
125 self.set_size_request(len(self.levelrms) * (self.CHANNEL_WIDTH + self.MARGIN) + self.LABEL_WIDTH, 100)
126 self.queue_draw()
00 #!/usr/bin/python3
11 import logging
2 import gi
3
4 gi.require_version('GstNet', '1.0')
5
26 from gi.repository import Gst, GstNet
37
48 __all__ = ['Clock']
0 #!/usr/bin/env python3
01 import os.path
12 import logging
2 from configparser import SafeConfigParser
33 from lib.args import Args
44 import lib.connection as Connection
5
5 from vocto.config import VocConfigParser
66 __all__ = ['Config']
77
88 Config = None
99
10 log = logging.getLogger('VoctoguiConfigParser')
1011
11 class VocConfigParser(SafeConfigParser):
12 def getlist(self, section, option):
13 option = self.get(section, option).strip()
14 if len(option) == 0:
15 return []
1612
17 unfiltered = [x.strip() for x in option.split(',')]
18 return list(filter(None, unfiltered))
13 class VoctoguiConfigParser(VocConfigParser):
1914
2015 def fetchServerConfig(self):
21 log = logging.getLogger('Config')
2216 log.info("reading server-config")
2317
2418 server_config = Connection.fetchServerConfig()
2620 log.info("merging server-config %s", server_config)
2721 self.read_dict(server_config)
2822
23 def getHost(self):
24 return Args.host if Args.host else self.get('server', 'host')
25
26 def getWindowSize(self):
27 if self.has_option('mainwindow', 'width') \
28 and self.has_option('mainwindow', 'height'):
29 # get size from config
30 return (self.getint('mainwindow', 'width'),
31 self.getint('mainwindow', 'height'))
32 else:
33 return None
34
35 def getForceFullScreen(self):
36 return self.getboolean('mainwindow', 'forcefullscreen', fallback=False)
37
38 def getShowCloseButton(self):
39 return self.getboolean('toolbar', 'close', fallback=True)
40
41 def getShowFullScreenButton(self):
42 return self.getboolean('toolbar', 'fullscreen', fallback=False)
43
44 def getShowQueueButton(self):
45 return self.getboolean('toolbar', 'queues', fallback=False)
46
47 def getShowPortButton(self):
48 return self.getboolean('toolbar', 'ports', fallback=True)
49
50 def getToolbarSourcesDefault(self):
51 return {"%s.name" % source:
52 source.upper()
53 for source in self.getList('mix', 'sources')
54 }
55
56 def trySection(self, section_name, default_result=None):
57 return self[section_name] if self.has_section(section_name) else default_result
58
59 def getToolbarSourcesA(self):
60 return self.trySection('toolbar.sources.a', self.getToolbarSourcesDefault())
61
62 def getToolbarSourcesB(self):
63 return self.trySection('toolbar.sources.b', self.getToolbarSourcesDefault())
64
65 def getToolbarCompositesDefault(self):
66 return {"%s.name" % composite.name:
67 composite.name.upper()
68 for composite in self.getTargetComposites()
69 }
70
71 def getToolbarComposites(self):
72 return self.trySection('toolbar.composites', self.getToolbarCompositesDefault())
73
74 def getToolbarMods(self):
75 return self.trySection('toolbar.mods', {})
76
77 def getToolbarMixDefault(self):
78 return {"retake.name": "RETAKE",
79 "cut.name": "CUT",
80 "trans.name": "TRANS"
81 }
82
83 def getToolbarMix(self):
84 return self.trySection('toolbar.mix', self.getToolbarMixDefault())
85
86 def getToolbarInsert(self):
87 return self.trySection('toolbar.insert', {})
88
2989
3090 def load():
3191 global Config
3292
33 files = [
34 os.path.join(os.path.dirname(os.path.realpath(__file__)),
35 '../default-config.ini'),
36 os.path.join(os.path.dirname(os.path.realpath(__file__)),
37 '../config.ini'),
38 '/etc/voctomix/voctogui.ini',
39 '/etc/voctogui.ini',
40 os.path.expanduser('~/.voctogui.ini'),
41 ]
93 Config = VoctoguiConfigParser()
4294
43 if Args.ini_file is not None:
44 files.append(Args.ini_file)
95 config_file_name = Args.ini_file if Args.ini_file else os.path.join(
96 os.path.dirname(os.path.realpath(__file__)), '../default-config.ini')
97 readfiles = Config.read([config_file_name])
4598
46 Config = VocConfigParser()
47 readfiles = Config.read(files)
48
49 log = logging.getLogger('ConfigParser')
50 log.debug('considered config-files: \n%s',
51 "\n".join([
52 "\t\t" + os.path.normpath(file)
53 for file in files
54 ]))
55 log.debug('successfully parsed config-files: \n%s',
56 "\n".join([
57 "\t\t" + os.path.normpath(file)
58 for file in readfiles
59 ]))
99 log.debug("successfully parsed config-file: '%s'", config_file_name)
60100
61101 if Args.ini_file is not None and Args.ini_file not in readfiles:
62102 raise RuntimeError('explicitly requested config-file "{}" '
00 import logging
11 import socket
22 import json
3 import sys
4
35 from queue import Queue
46 from gi.repository import Gtk, GObject
7
8 from vocto.port import Port
59
610 log = logging.getLogger('Connection')
711 conn = None
812 ip = None
9 port = 9999
1013 command_queue = Queue()
1114 signal_handlers = {}
1215
1316
1417 def establish(host):
15 global conn, port, log, ip
18 global conn, log, ip
1619
1720 log.info('establishing Connection to %s', host)
18 conn = socket.create_connection((host, port))
19 log.debug(r'Connection successful \o/')
21 try:
22 conn = socket.create_connection((host, Port.CORE_LISTENING))
23 log.info("Connection to host %s at port %d successful" % (host, Port.CORE_LISTENING) )
24 except ConnectionRefusedError:
25 log.error("Connecting to %s at port %d has failed. Is voctocore running? Can you ping the host?" % (host, Port.CORE_LISTENING) )
26 sys.exit(-1)
2027
2128 ip = conn.getpeername()[0]
2229 log.debug('Remote-IP is %s', ip)
116123
117124 signal = words[0]
118125 args = words[1:]
119
120 log.info('received signal %s, dispatching', signal)
126 if signal == "error":
127 log.error('received error: %s', line )
121128 if signal not in signal_handlers:
122129 return True
123130
1414 c_mod = 32
1515 c_msg = 0
1616
17 if record.levelno == logging.WARNING:
17 if record.levelno <= logging.DEBUG:
18 c_msg = 90
19
20 elif record.levelno <= logging.INFO:
21 c_lvl = 37
22 c_msg = 97
23
24 elif record.levelno <= logging.WARNING:
1825 c_lvl = 31
19 # c_mod = 33
2026 c_msg = 33
2127
22 elif record.levelno > logging.WARNING:
28 else:
2329 c_lvl = 31
2430 c_mod = 31
2531 c_msg = 31
0 #!/usr/bin/env python3
1 import logging
2 import os
3 import json
4 from gi.repository import Gtk, Gst, GLib
5
6 from lib.config import Config
7 from lib.uibuilder import UiBuilder
8 import lib.connection as Connection
9 from vocto.port import Port
10
11 # time interval to re-fetch queue timings
12 TIMER_RESOLUTION = 5.0
13
14 COLOR_OK = ("white", "darkgreen")
15 COLOR_WARN = ("darkred", "darkorange")
16 COLOR_ERROR = ("white", "red")
17
18
19 class PortsWindowController():
20
21 def __init__(self, uibuilder):
22 self.log = logging.getLogger('QueuesWindowController')
23
24 # get related widgets
25 self.win = uibuilder.get_check_widget('ports_win')
26 self.store = uibuilder.get_check_widget('ports_store')
27 self.scroll = uibuilder.get_check_widget('ports_scroll')
28 self.title = uibuilder.get_check_widget('ports_title')
29 self.title.set_title("VOC2CORE {}".format(Config.getHost()))
30 # remember row iterators
31 self.iterators = None
32
33 # listen for queue_report from voctocore
34 Connection.on('port_report', self.on_port_report)
35
36 def on_port_report(self, *report):
37
38 def color(port):
39 if port.connections > 0:
40 return COLOR_OK
41 else:
42 return COLOR_ERROR if port.is_input() else COLOR_WARN
43
44 # read string report into dictonary
45 report = json.loads("".join(report))
46 # check if this is the initial report
47 if not self.iterators:
48 # append report as rows to treeview store and remember row iterators
49 self.iterators = dict()
50 for p in report:
51 port = Port.from_str(p)
52 self.iterators[port.port] = self.store.append((
53 port.name,
54 port.audio,
55 port.video,
56 "IN" if port.is_input() else "OUT",
57 port.port,
58 *color(port)
59 ))
60 else:
61 # just update values of second column
62 for p in report:
63 port = Port.from_str(p)
64 it = self.iterators[port.port]
65 self.store.set_value(it, 0, port.name)
66 self.store.set_value(it, 1, port.audio)
67 self.store.set_value(it, 2, port.video)
68 self.store.set_value(it, 3, "IN" if port.is_input() else "OUT")
69 self.store.set_value(it, 4, port.port)
70 self.store.set_value(it, 5, color(port)[0])
71 self.store.set_value(it, 6, color(port)[1])
72
73 def show(self, visible=True):
74 # check if widget is getting visible
75 if visible:
76 # request queue timing report from voctocore
77 Connection.send('report_ports')
78 # schedule repetition
79 GLib.timeout_add(TIMER_RESOLUTION * 1000, self.do_timeout)
80 # do the boring stuff
81 self.win.show()
82 else:
83 self.win.hide()
84
85 def do_timeout(self):
86 # re-request queue report
87 Connection.send('report_ports')
88 # repeat if widget is visible
89 return self.win.is_visible()
0 #!/usr/bin/env python3
1 import logging
2 import os
3 import json
4 from gi.repository import Gtk, Gst, GLib
5
6 from lib.config import Config
7 from lib.uibuilder import UiBuilder
8 import lib.connection as Connection
9
10 # time interval to re-fetch queue timings
11 TIMER_RESOLUTION = 1.0
12
13 class QueuesWindowController():
14
15 def __init__(self,uibuilder):
16 self.log = logging.getLogger('QueuesWindowController')
17
18 # get related widgets
19 self.win = uibuilder.get_check_widget('queue_win')
20 self.store = uibuilder.get_check_widget('queue_store')
21 self.scroll = uibuilder.get_check_widget('queue_scroll')
22
23 # remember row iterators
24 self.iterators = None
25
26 # listen for queue_report from voctocore
27 Connection.on('queue_report', self.on_queue_report)
28
29 def on_queue_report(self, *report):
30 # read string report into dictonary
31 report = json.loads("".join(report))
32 # check if this is the initial report
33 if not self.iterators:
34 # append report as rows to treeview store and remember row iterators
35 self.iterators = dict()
36 for queue, time in report.items():
37 self.iterators[queue] = self.store.append((queue, time / Gst.SECOND))
38 else:
39 # just update values of second column
40 for queue, time in report.items():
41 self.store.set_value(self.iterators[queue], 1, time / Gst.SECOND)
42
43 def show(self,visible=True):
44 # check if widget is getting visible
45 if visible:
46 # request queue timing report from voctocore
47 Connection.send('report_queues')
48 # schedule repetition
49 GLib.timeout_add(TIMER_RESOLUTION * 1000, self.do_timeout)
50 # do the boring stuff
51 self.win.show()
52 else:
53 self.win.hide()
54
55 def do_timeout(self):
56 # re-request queue report
57 Connection.send('report_queues')
58 # repeat if widget is visible
59 return self.win.is_visible()
+0
-83
voctogui/lib/shortcuts.py less more
0 from gi.repository import Gtk
1
2 from lib.config import Config
3
4
5 if hasattr(Gtk, "ShortcutsWindow"):
6 def show_shortcuts(win):
7 shortcuts_window = ShortcutsWindow(win)
8 shortcuts_window.show()
9
10 class ShortcutsWindow(Gtk.ShortcutsWindow):
11 def __init__(self, win):
12 Gtk.ShortcutsWindow.__init__(self)
13 self.build()
14 self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
15 self.set_transient_for(win)
16 self.set_modal(True)
17
18 def build(self):
19 section = Gtk.ShortcutsSection()
20 section.show()
21
22 compose_group = Gtk.ShortcutsGroup(title="Composition modes")
23 compose_group.show()
24 for accel, desc in [("F1", "Select fullscreen mode"),
25 ("F2", "Select Picture in Picture mode"),
26 ("F3", "Select Side-by-Side Equal mode"),
27 ("F4", "Select Side-by-Side Preview mode")]:
28 short = Gtk.ShortcutsShortcut(title=desc, accelerator=accel)
29 short.show()
30 compose_group.add(short)
31 section.add(compose_group)
32
33 if Config.getboolean('stream-blanker', 'enabled'):
34 blank_group = self._build_blank_group()
35 section.add(blank_group)
36
37 source_group = Gtk.ShortcutsGroup(title="Source Selection")
38 source_group.show()
39 num = len(Config.getlist('mix', 'sources'))
40 source_items = [
41 ("1...{}".format(num),
42 "Select Source as A-Source"),
43 ("<ctrl>1...<ctrl>{}".format(num),
44 "Select Source as B-Source"),
45 ("<alt>1...<alt>{}".format(num),
46 "Select Source as Fullscreen")
47 ]
48 for accel, desc in source_items:
49 short = Gtk.ShortcutsShortcut(title=desc, accelerator=accel)
50 short.show()
51 source_group.add(short)
52 section.add(source_group)
53
54 if Config.getboolean('misc', 'cut'):
55 other_group = Gtk.ShortcutsGroup(title="Other")
56 other_group.show()
57 short = Gtk.ShortcutsShortcut(title="Send Cut message",
58 accelerator="t")
59 short.show()
60 other_group.add(short)
61 section.add(other_group)
62
63 self.add(section)
64
65 def _build_blank_group(self):
66 blank_group = Gtk.ShortcutsGroup(title="Stream blanking")
67 blank_group.show()
68 blank_sources = Config.getlist('stream-blanker', 'sources')
69 blank_items = [
70 ("F{}".format(12 - len(blank_sources) + i),
71 "Set stream to {}".format(source))
72 for i, source in enumerate(reversed(blank_sources))
73 ]
74 blank_items.append(("F12", "Set stream Live"))
75 for accel, desc in blank_items:
76 short = Gtk.ShortcutsShortcut(title=desc, accelerator=accel)
77 short.show()
78 blank_group.add(short)
79 return blank_group
80 else:
81 def show_shortcuts(win):
82 pass
4444 # setup gradients for clock background to get a smooth border
4545 bg_lg = cairo.RadialGradient(
4646 center[0], center[1], 0, center[0], center[1], radius)
47 bg_lg.add_color_stop_rgba(0.0, 0, 0, 0, 1.0)
48 bg_lg.add_color_stop_rgba(0.9, 0, 0, 0, 1.0)
49 bg_lg.add_color_stop_rgba(1.0, 0, 0, 0, 0.0)
47 bg_lg.add_color_stop_rgba(0.0, 0.1, 0.1, 0.1, 1.0)
48 bg_lg.add_color_stop_rgba(0.9, 0.1, 0.1, 0.1, 1.0)
49 bg_lg.add_color_stop_rgba(1.0, 0.2, 0.2, 0.2, 0.0)
5050 # paint background
5151 cr.set_source(bg_lg)
5252 cr.arc(center[0], center[1], radius, 0, 2 * math.pi)
7777 cr.arc(pos[0], pos[1], radius / 40, 0, 2 * math.pi)
7878 cr.fill()
7979 # set a reasonable font size
80 cr.set_font_size(cr.user_to_device_distance(0, height / 5)[1])
80 cr.select_font_face("Roboto Condensed")
81 cr.set_font_size(cr.user_to_device_distance(0, height / 4)[1])
8182 # format time into a string
8283 text = time.strftime("%H:%M")
8384 # get text drawing extents
0 #!/usr/bin/env python3
1 import logging
2 import os
3
4 import time
5
6 from gi.repository import Gtk, GLib
7 import lib.connection as Connection
8
9 from lib.config import Config
10
11
12 class BlinderToolbarController(object):
13 """Manages Accelerators and Clicks on the Composition Toolbar-Buttons"""
14
15 # set resolution of the blink timer in seconds
16 timer_resolution = 1.0
17
18 def __init__(self, win, uibuilder):
19 self.log = logging.getLogger('BlinderToolbarController')
20 self.toolbar = uibuilder.find_widget_recursive(win, 'toolbar_blinder')
21
22 live_button = uibuilder.find_widget_recursive(self.toolbar, 'stream_live')
23 blind_button = uibuilder.find_widget_recursive(
24 self.toolbar, 'stream_blind')
25 blinder_box = uibuilder.find_widget_recursive(
26 win, 'box_blinds')
27
28 blind_button_pos = self.toolbar.get_item_index(blind_button)
29
30 if not Config.getBlinderEnabled():
31 self.log.info('disabling blinding features '
32 'because the server does not support them')
33
34 self.toolbar.remove(live_button)
35 self.toolbar.remove(blind_button)
36
37 # hide blinder box
38 blinder_box.hide()
39 blinder_box.set_no_show_all(True)
40 return
41
42 blinder_sources = Config.getBlinderSources()
43
44 self.current_status = None
45
46 live_button.connect('toggled', self.on_btn_toggled)
47 live_button.set_can_focus(False)
48 self.live_button = live_button
49 self.blind_buttons = {}
50
51 accel_f_key = 11
52
53 for idx, name in enumerate(blinder_sources):
54 if idx == 0:
55 new_btn = blind_button
56 else:
57 new_btn = Gtk.RadioToolButton(group=live_button)
58 self.toolbar.insert(new_btn, blind_button_pos)
59
60 new_btn.set_name(name)
61 new_btn.get_style_context().add_class("output")
62 new_btn.get_style_context().add_class("mode")
63 new_btn.set_can_focus(False)
64 new_btn.set_label(name.upper())
65 new_btn.connect('toggled', self.on_btn_toggled)
66 new_btn.set_tooltip_text("Stop streaming by %s" % name)
67
68 self.blind_buttons[name] = new_btn
69 accel_f_key = accel_f_key - 1
70
71 # connect event-handler and request initial state
72 Connection.on('stream_status', self.on_stream_status)
73 Connection.send('get_stream_status')
74 self.timeout = None
75
76 def start_blink(self):
77 self.blink = True
78 self.do_timeout()
79 self.blink = True
80 # remove old time out
81 if self.timeout:
82 GLib.source_remove(self.timeout)
83 # set up timeout for periodic redraw
84 self.timeout = GLib.timeout_add_seconds(self.timer_resolution, self.do_timeout)
85
86 def on_btn_toggled(self, btn):
87 if btn.get_active():
88 btn_name = btn.get_name()
89 if self.current_status != btn_name:
90 self.log.info('stream-status activated: %s', btn_name)
91 if btn_name == 'live':
92 Connection.send('set_stream_live')
93 else:
94 Connection.send('set_stream_blind', btn_name)
95
96 def on_stream_status(self, status, source=None):
97 self.log.info('on_stream_status callback w/ status %s and source %s',
98 status, source)
99
100 self.current_status = source if source is not None else status
101 for button in list(self.blind_buttons.values()) + [self.live_button]:
102 if button.get_name() == self.current_status:
103 button.set_active(True)
104 self.start_blink()
105
106 def do_timeout(self):
107 # if time did not change since last redraw
108 for button in list(self.blind_buttons.values()) + [self.live_button]:
109 if self.blink:
110 button.get_style_context().add_class("blink")
111 else:
112 button.get_style_context().remove_class("blink")
113 self.blink = not self.blink
114 return True
0 #!/usr/bin/env python3
1 from gi.repository import Gtk
2 import sys
3 from lib.toolbar.widgets import _decode, Widgets
4
5 class Buttons(Widgets):
6 ''' reads toolbar buttons from configuration and adds them into a toolbar
7 items from INI file can shall look like this:
8
9 additional some attributes will be added automatically:
10
11 'button' is the created button instance
12 '''
13
14 def __init__(self, cfg_items):
15 super().__init__(cfg_items,"buttons")
16
17 def create(self, toolbar, accelerators=None, callback=None, css=[], group=True, radio=True, sensitive=True, visible=True, multiline_names=True):
18 ''' create toolbar from read configuration items '''
19
20 # generate a list of all buttons
21 buttons = []
22 first_btn = None
23 for id, attr in self.items():
24 if radio:
25 # create button and manage grouping of radio buttons
26 if group:
27 if not first_btn:
28 first_btn = btn = Gtk.RadioToolButton(None)
29 else:
30 btn = Gtk.RadioToolButton.new_from_widget(first_btn)
31 else:
32 btn = Gtk.ToggleToolButton()
33 else:
34 btn = Gtk.ToolButton()
35
36 # set button properties
37 self.add(btn, id, accelerators, callback, ('toggled' if radio else 'clicked'), css, sensitive, visible, multiline_names)
38 btn.set_visible_horizontal(visible)
39 btn.set_visible_vertical(visible)
40 btn.set_can_focus(False)
41
42 # remember created button in attributes
43 attr['button'] = btn
44
45 # store button
46 buttons.append(
47 (int(attr['pos']) if 'pos' in attr else sys.maxsize, btn))
48
49 # add all buttons in right order
50 def key(x):
51 return x[0]
52 pos = toolbar.get_n_items()
53 for btn in sorted(buttons, key=key):
54 toolbar.insert(btn[1], pos)
55 pos += 1
+0
-118
voctogui/lib/toolbar/composition.py less more
0 import logging
1
2 from gi.repository import Gtk
3 import lib.connection as Connection
4
5 from lib.config import Config
6
7
8 class CompositionToolbarController(object):
9 """Manages Accelerators and Clicks on the Composition Toolbar-Buttons"""
10
11 def __init__(self, toolbar, win, uibuilder):
12 self.log = logging.getLogger('CompositionToolbarController')
13
14 accelerators = Gtk.AccelGroup()
15 win.add_accel_group(accelerators)
16
17 composites = [
18 'picture_in_picture',
19 'side_by_side_equal',
20 'side_by_side_preview'
21 ]
22
23 sources = Config.getlist('mix', 'sources')
24
25 self.composite_btns = {}
26 self.current_composition = None
27
28 fullscreen_btn = uibuilder.find_widget_recursive(
29 toolbar, 'composite-fullscreen')
30
31 fullscreen_btn_pos = toolbar.get_item_index(fullscreen_btn)
32
33 accel_f_key = 1
34
35 for idx, name in enumerate(sources):
36 key, mod = Gtk.accelerator_parse('F%u' % accel_f_key)
37
38 if idx == 0:
39 new_btn = fullscreen_btn
40 else:
41 new_icon = Gtk.Image.new_from_pixbuf(
42 fullscreen_btn.get_icon_widget().get_pixbuf())
43 new_btn = Gtk.RadioToolButton(group=fullscreen_btn)
44 new_btn.set_icon_widget(new_icon)
45 toolbar.insert(new_btn, fullscreen_btn_pos + idx)
46
47 new_btn.set_label("Fullscreen %s\nF%s" % (name, accel_f_key))
48 new_btn.connect('toggled', self.on_btn_toggled)
49 new_btn.set_name('fullscreen %s' % name)
50
51 tooltip = Gtk.accelerator_get_label(key, mod)
52 new_btn.set_tooltip_text(tooltip)
53
54 new_btn.get_child().add_accelerator(
55 'clicked', accelerators,
56 key, mod, Gtk.AccelFlags.VISIBLE)
57
58 self.composite_btns['fullscreen %s' % name] = new_btn
59 accel_f_key = accel_f_key + 1
60
61 for idx, name in enumerate(composites):
62 key, mod = Gtk.accelerator_parse('F%u' % accel_f_key)
63
64 btn = uibuilder.find_widget_recursive(
65 toolbar,
66 'composite-' + name.replace('_', '-')
67 )
68 btn.set_name(name)
69
70 btn.set_label(btn.get_label() + "\nF%s" % accel_f_key)
71
72 tooltip = Gtk.accelerator_get_label(key, mod)
73 btn.set_tooltip_text(tooltip)
74
75 # Thanks to http://stackoverflow.com/a/19739855/1659732
76 btn.get_child().add_accelerator('clicked', accelerators,
77 key, mod, Gtk.AccelFlags.VISIBLE)
78 btn.connect('toggled', self.on_btn_toggled)
79
80 self.composite_btns[name] = btn
81 accel_f_key = accel_f_key + 1
82
83 # connect event-handler and request initial state
84 Connection.on('composite_mode_and_video_status',
85 self.on_composite_mode_and_video_status)
86
87 Connection.send('get_composite_mode_and_video_status')
88
89 def on_btn_toggled(self, btn):
90 if not btn.get_active():
91 return
92
93 btn_name = btn.get_name()
94 self.log.info('btn_name = %s', btn_name)
95 if self.current_composition == btn_name:
96 self.log.info('composition-mode already active: %s', btn_name)
97 return
98
99 self.log.info('composition-mode activated: %s', btn_name)
100
101 if btn_name.startswith('fullscreen'):
102 _, source_name = btn_name.split(' ', 1)
103 Connection.send('set_videos_and_composite',
104 source_name, '*', 'fullscreen')
105
106 else:
107 Connection.send('set_composite_mode', btn_name)
108
109 def on_composite_mode_and_video_status(self, mode, source_a, source_b):
110 self.log.info('composite_mode_and_video_status callback w/ '
111 'mode: %s, source a: %s, source b: %s',
112 mode, source_a, source_b)
113 if mode == 'fullscreen':
114 mode = 'fullscreen %s' % source_a
115
116 self.current_composition = mode
117 self.composite_btns[mode].set_active(True)
0 #!/usr/bin/env python3
1 import os
2
3 def mark_label(btn):
4 label = btn.get_label()
5 label = label.replace('▶ ','').replace(' ◀','')
6 label = "▶ " + label + " ◀"
7 btn.set_label(label)
8
9 def unmark_label(btn):
10 label = btn.get_label()
11 label = label.replace('▶ ','').replace(' ◀','')
12 btn.set_label(label)
13
14 def top_dir_path():
15 return os.path.dirname(os.path.abspath(__file__ + '/../..'))
16
17
18 def icon_path():
19 return os.path.join(top_dir_path(), 'ui')
0 #!/usr/bin/env python3
01 import logging
1 from gi.repository import Gtk
2 from gi.repository import Gdk, Gtk
23
34 from lib.config import Config
45 import lib.connection as Connection
78 class MiscToolbarController(object):
89 """Manages Accelerators and Clicks Misc buttons"""
910
10 def __init__(self, toolbar, win, uibuilder):
11 def __init__(self, win, uibuilder, queues_controller, ports_controller, video_display):
12 self.win = win
1113 self.log = logging.getLogger('MiscToolbarController')
14 self.toolbar = uibuilder.find_widget_recursive(win, 'toolbar_main')
1215
1316 # Accelerators
1417 accelerators = Gtk.AccelGroup()
1518 win.add_accel_group(accelerators)
1619
17 closebtn = uibuilder.find_widget_recursive(toolbar, 'close')
18 closebtn.set_visible(Config.getboolean('misc', 'close'))
20 closebtn = uibuilder.find_widget_recursive(self.toolbar, 'close')
21 closebtn.set_visible(Config.getShowCloseButton())
1922 closebtn.connect('clicked', self.on_closebtn_clicked)
2023
21 cutbtn = uibuilder.find_widget_recursive(toolbar, 'cut')
22 cutbtn.set_visible(Config.getboolean('misc', 'cut'))
23 cutbtn.connect('clicked', self.on_cutbtn_clicked)
24 fullscreenbtn = uibuilder.find_widget_recursive(self.toolbar, 'fullscreen')
25 fullscreenbtn.set_visible(Config.getShowFullScreenButton())
26 fullscreenbtn.connect('clicked', self.on_fullscreenbtn_clicked)
27 key, mod = Gtk.accelerator_parse('F11')
28 fullscreenbtn.add_accelerator('clicked', accelerators,
29 key, mod, Gtk.AccelFlags.VISIBLE)
30 self.fullscreen_button = fullscreenbtn
31
32 mutebtn = uibuilder.find_widget_recursive(self.toolbar, 'mute_button')
33 if Config.getPlayAudio():
34 mutebtn.set_active(True)
35 mutebtn.connect('clicked', self.on_mutebtn_clicked)
36 self.video_display = video_display
37 else:
38 mutebtn.set_no_show_all(True)
39 mutebtn.hide()
40
41 queues_button = uibuilder.find_widget_recursive(self.toolbar, 'queue_button')
42 queues_button.set_visible(Config.getShowQueueButton())
43 queues_button.connect('toggled', self.on_queues_button_toggled)
44 self.queues_controller = queues_controller
45
46 ports_button = uibuilder.find_widget_recursive(self.toolbar, 'ports_button')
47 ports_button.set_visible(Config.getShowPortButton())
48 ports_button.connect('toggled', self.on_ports_button_toggled)
49 self.ports_controller = ports_controller
2450
2551 key, mod = Gtk.accelerator_parse('t')
26 cutbtn.add_accelerator('clicked', accelerators,
27 key, mod, Gtk.AccelFlags.VISIBLE)
2852 tooltip = Gtk.accelerator_get_label(key, mod)
29 cutbtn.set_tooltip_text(tooltip)
53
54 # Controller for fullscreen behavior
55 self.__is_fullscreen = False
56 win.connect("window-state-event", self.on_window_state_event)
3057
3158 def on_closebtn_clicked(self, btn):
3259 self.log.info('close-button clicked')
3360 Gtk.main_quit()
3461
35 def on_cutbtn_clicked(self, btn):
36 self.log.info('cut-button clicked')
37 Connection.send('message', 'cut')
62 def on_fullscreenbtn_clicked(self, btn):
63 if not self.within_state_event:
64 self.log.info('fullscreen-button clicked')
65 if self.__is_fullscreen:
66 self.win.unfullscreen()
67 else:
68 self.win.fullscreen()
69
70 def on_mutebtn_clicked(self, btn):
71 self.log.info('mute-button clicked')
72 self.video_display.mute(not btn.get_active())
73
74 def on_queues_button_toggled(self, btn):
75 self.log.info('queues-button clicked')
76 self.queues_controller.show(btn.get_active())
77
78 def on_ports_button_toggled(self, btn):
79 self.log.info('queues-button clicked')
80 self.ports_controller.show(btn.get_active())
81
82 def on_window_state_event(self, widget, ev):
83 self.within_state_event = True
84 self.__is_fullscreen = bool(ev.new_window_state & Gdk.WindowState.FULLSCREEN)
85 self.fullscreen_button.set_active(self.__is_fullscreen)
86 self.within_state_event = False
0 #!/usr/bin/env python3
1 import os
2 import logging
3
4 from gi.repository import Gtk
5 import lib.connection as Connection
6
7 from lib.config import Config
8 from vocto.composite_commands import CompositeCommand
9 from lib.toolbar.buttons import Buttons
10 from lib.uibuilder import UiBuilder
11
12
13 class MixToolbarController(object):
14 """Manages Accelerators and Clicks on the Preview Composition Toolbar-Buttons"""
15
16 def __init__(self, win, uibuilder, preview_controller, overlay_controller):
17 self.initialized = False
18 self.preview_controller = preview_controller
19 self.overlay_controller = overlay_controller
20 self.log = logging.getLogger('PreviewToolbarController')
21
22 accelerators = Gtk.AccelGroup()
23 win.add_accel_group(accelerators)
24
25 self.mix = Buttons(Config.getToolbarMix())
26
27 self.toolbar = uibuilder.find_widget_recursive(win, 'toolbar_mix')
28
29 self.mix.create(self.toolbar, accelerators,
30 self.on_btn_clicked, radio=False)
31 Connection.on('best', self.on_best)
32
33 def on_btn_clicked(self, btn):
34 id = btn.get_name()
35
36 # on transition hide overlay if AUTO-OFF is on
37 if self.overlay_controller.isAutoOff() and id != 'retake':
38 Connection.send('show_overlay',str(False))
39
40 command = self.preview_controller.command()
41 output = self.preview_controller.output
42 if command.A == output.A and command.B != output.B:
43 output.B = command.B
44 if command.B == output.B and command.A != output.A:
45 output.A = command.A
46 self.preview_controller.set_command(output,False)
47 if id == 'cut':
48 self.log.info('Sending new composite: %s', command)
49 Connection.send('cut', str(command))
50 elif id == 'trans':
51 self.log.info(
52 'Sending new composite (using transition): %s', command)
53 Connection.send('transition', str(command))
54 else:
55 Connection.send('get_composite')
56 self.mix['retake']['button'].set_sensitive(self.preview_controller.command() != self.preview_controller.output)
57
58 def on_best(self, best, targetA, targetB):
59 command = self.preview_controller.command()
60 self.mix['retake']['button'].set_sensitive(command != self.preview_controller.output)
61 self.mix['trans']['button'].set_sensitive(best == "transition")
62 self.mix['cut']['button'].set_sensitive((best == "transition" or best == "cut") and not (command.composite == "lec" or command.composite == "|lec"))
0 #!/usr/bin/env python3
1 import os
2 import logging
3
4 from gi.repository import Gtk
5 import lib.connection as Connection
6
7 from lib.config import Config
8 from lib.uibuilder import UiBuilder
9 from lib.toolbar.widgets import Widgets
10 from datetime import datetime, timedelta
11 from vocto.command_helpers import quote, dequote, str2bool
12
13 class OverlayToolbarController(object):
14 """Manages Accelerators and Clicks on the Overlay Composition Toolbar-Buttons"""
15
16 def __init__(self, win, uibuilder):
17 self.initialized = False
18
19 self.log = logging.getLogger('OverlayToolbarController')
20
21 accelerators = Gtk.AccelGroup()
22 win.add_accel_group(accelerators)
23
24 if Config.hasOverlay():
25
26 widgets = Widgets(Config.getToolbarInsert())
27
28 # connect to inserts selection combo box
29 self.inserts = uibuilder.get_check_widget('inserts')
30 self.inserts_store = uibuilder.get_check_widget('insert-store')
31 self.inserts.connect('changed', self.on_inserts_changed)
32
33 # connect to INSERT toggle button
34 self.insert = uibuilder.get_check_widget('insert')
35 widgets.add(self.insert, 'insert', accelerators, self.on_insert_toggled, signal='toggled' )
36
37 self.update_inserts = uibuilder.get_check_widget('update-inserts')
38 widgets.add(self.update_inserts, 'update', accelerators, self.update_overlays)
39
40 # initialize to AUTO-OFF toggle button
41 self.autooff = uibuilder.get_check_widget('insert-auto-off')
42 self.autooff.set_visible(Config.getOverlayUserAutoOff())
43 self.autooff.set_active(Config.getOverlayAutoOff())
44 widgets.add(self.autooff, 'auto-off', accelerators)
45
46 # remember overlay description label
47 self.overlay_description = uibuilder.get_check_widget(
48 'overlay-description')
49
50 # initialize our overlay list until we get one from the core
51 self.overlays = []
52
53 # what we receive from core
54 Connection.on('overlays', self.on_overlays)
55 Connection.on('overlays_title', self.on_overlays_title)
56 Connection.on('overlay', self.on_overlay)
57 Connection.on('overlay_visible', self.on_overlay_visible)
58 # call core for a list of available overlays
59 self.update_overlays()
60 # show insert tool bar
61 uibuilder.get_check_widget('box_insert').show()
62 else:
63 # hide insert tool bar
64 uibuilder.get_check_widget('box_insert').hide()
65
66 # Hint: self.initialized will be set to True in response to 'get_overlay'
67
68 def on_insert_toggled(self, btn):
69 # can't select insert, if we got no list already
70 if not self.initialized:
71 return
72 Connection.send('show_overlay', str(self.insert.get_active()))
73
74 def on_inserts_changed(self, combobox):
75 ''' new insert was selected
76 '''
77 # can't select insert, if we got no list already
78 if not self.initialized:
79 return
80 # check if there is any useful selection
81 if self.inserts.get_active_iter():
82 # get name of the selection
83 selected_overlay = self.inserts_store[self.inserts.get_active_iter(
84 )][0]
85 # tell log about user selection
86 self.log.info("setting overlay to '%s'", selected_overlay)
87 # hide overlay if 'AUTO-OFF' is selected
88 if self.isAutoOff():
89 Connection.send('show_overlay', str(False))
90 # select overlay on voctocore
91 Connection.send('set_overlay', quote(str(selected_overlay)))
92
93 def on_overlay_visible(self, visible):
94 ''' receive overlay visibility
95 '''
96 # set 'insert' button state
97 self.insert.set_active(str2bool(visible))
98
99 def on_overlay(self, overlay):
100 # decode parameter
101 overlay = dequote(overlay)
102 overlays = [o for o, t in self.overlays]
103 # do we know this overlay?
104 if overlay in overlays:
105 # select overlay by name
106 self.inserts.set_active(overlays.index(overlay))
107 else:
108 if self.overlays:
109 # select first item as default
110 self.inserts.set_active(0)
111 # tell log about new overlay
112 self.log.info("overlay is '%s'", overlay)
113 # enable 'INSERT' button if there is a selection
114 self.insert.set_sensitive(not self.inserts.get_active_iter() is None)
115
116 def on_overlays(self, overlays):
117 # decode parameter
118 overlays = [dequote(o).split('|') for o in overlays.split(",")]
119 overlays = [o if len(o) == 2 else (o[0], o[0]) for o in overlays]
120 # tell log about overlay list
121 self.log.info("Got list of overlays from server '%s'", overlays)
122 # clear inserts storage
123 self.inserts_store.clear()
124 # save inserts into storage if there are any
125 if overlays:
126 for o in overlays:
127 self.inserts_store.append(o)
128 # enable selection widget only if available
129 self.inserts.set_sensitive(len(overlays) > 1 if overlays else False)
130 # remember overlay list
131 self.overlays = overlays
132 # we have a list of overlays
133 self.initialized = True
134 # poll voctocore's current overlay selection
135 Connection.send('get_overlay_visible')
136 Connection.send('get_overlay')
137
138 def on_overlays_title(self, title):
139 # decode parameter
140 title = [dequote(t) for t in title.split(",")]
141 # title given?
142 if title:
143 # show title
144 if len(title) == 4:
145 start, end, id, text = title
146 self.overlay_description.set_text(
147 "{start} - {end} : #{id} '{text}'".format(start=start.split(" ")[1],
148 end=end.split(" ")[1],
149 id=id,
150 text=text))
151 else:
152 self.overlay_description.set_text(title[0])
153 self.overlay_description.show()
154 else:
155 # hide title
156 self.overlay_description.hide()
157 # tell log about overlay list
158 self.log.info("Got title of overlays from server '%s'", title)
159
160 def update_overlays(self,btn=None):
161 Connection.send('get_overlays')
162 Connection.send('get_overlays_title')
163
164 def isAutoOff(self):
165 if Config.hasOverlay():
166 return self.autooff.get_active()
0 #!/usr/bin/env python3
1 import os
2 import logging
3 import copy
4
5 from gi.repository import Gtk
6 import lib.connection as Connection
7
8 from lib.config import Config
9 from vocto.composite_commands import CompositeCommand
10 from lib.toolbar.buttons import Buttons
11 from lib.uibuilder import UiBuilder
12
13
14 class PreviewToolbarController(object):
15 """Manages Accelerators and Clicks on the Preview Composition Toolbar-Buttons"""
16
17 def __init__(self, win, uibuilder):
18 self.initialized = False
19
20 self.log = logging.getLogger('PreviewToolbarController')
21
22 accelerators = Gtk.AccelGroup()
23 win.add_accel_group(accelerators)
24
25 self.sourcesA = Buttons(Config.getToolbarSourcesA())
26 self.sourcesB = Buttons(Config.getToolbarSourcesB())
27 self.composites = Buttons(Config.getToolbarComposites())
28 self.mods = Buttons(Config.getToolbarMods())
29
30 toolbar_composite = uibuilder.find_widget_recursive(
31 win, 'toolbar_preview_composite')
32 toolbar_a = uibuilder.find_widget_recursive(win, 'toolbar_preview_a')
33 toolbar_b = uibuilder.find_widget_recursive(win, 'toolbar_preview_b')
34 toolbar_mod = uibuilder.find_widget_recursive(
35 win, 'toolbar_preview_mod')
36
37 self.frame_b = uibuilder.find_widget_recursive(win, 'frame_preview_b')
38
39 # hide modify box if not needed
40 box_modify = uibuilder.find_widget_recursive(win, 'box_preview_modify')
41 if not Config.getToolbarMods():
42 box_modify.hide()
43 box_modify.set_no_show_all(True)
44
45 self.composites.create(toolbar_composite,accelerators, self.on_btn_toggled)
46 self.sourcesA.create(toolbar_a, accelerators, self.on_btn_toggled)
47 self.sourcesB.create(toolbar_b, accelerators, self.on_btn_toggled)
48 self.mods.create(toolbar_mod, accelerators, self.on_btn_toggled, group=False)
49
50 self.invalid_buttons = []
51 self.validate(self.sourcesA)
52 self.validate(self.sourcesB)
53
54 # initialize source buttons
55 self.sourceA = Config.getSources()[0]
56 self.sourceB = Config.getSources()[1]
57 self.sourcesA[self.sourceA]['button'].set_active(True)
58 self.sourcesB[self.sourceB]['button'].set_active(True)
59
60 self.composite = self.composites.ids[0]
61 self.composites[self.composite]['button'].set_active(True)
62
63 self.modstates = dict()
64 for id in self.mods.ids:
65 self.modstates[id] = False
66
67 # load composites from config
68 self.log.info("Reading transitions configuration...")
69 self.composites_ = Config.getComposites()
70
71 Connection.on('best', self.on_best)
72 Connection.on('composite', self.on_composite)
73 Connection.send('get_composite')
74 self.enable_modifiers()
75 self.enable_sourcesB()
76 self.enable_sources();
77
78 self.do_test = True
79 self.initialized = True
80
81 def on_btn_toggled(self, btn):
82 if not self.initialized:
83 return
84
85 id = btn.get_name()
86 if btn.get_active():
87 # sources button toggled?
88 if id in self.sourcesA or id in self.sourcesB:
89 # check for A and B switch to the same source and fix it
90 if self.sourcesA[id]['button'] == btn:
91 if id in self.sourcesB and self.sourcesB[id]['button'].get_active():
92 self.sourceB = None
93 self.sourcesB[self.sourceA]['button'].set_active(True)
94 self.sourceA = id
95 self.log.info(
96 "Selected '%s' for preview source A", self.sourceA)
97 elif self.sourcesB[id]['button'] == btn:
98 if self.sourcesA[id]['button'].get_active():
99 self.sourceA = None
100 self.sourcesA[self.sourceB]['button'].set_active(True)
101 self.sourceB = id
102 self.log.info(
103 "Selected '%s' for preview source B", self.sourceB)
104 self.test()
105 elif id in self.composites:
106 self.composite = id
107 self.enable_sourcesB()
108 self.enable_modifiers()
109 self.log.info(
110 "Selected '%s' for preview target composite", self.composite)
111 self.test()
112 if id in self.mods:
113 self.modstates[id] = btn.get_active()
114 self.log.info("Turned preview modifier '%s' %s", id,
115 'on' if self.modstates[id] else 'off')
116 self.test()
117 self.enable_sources();
118 self.log.debug("current command is '%s", self.command())
119
120 def enable_modifiers(self):
121 command = CompositeCommand(self.composite, self.sourceA, self.sourceB)
122 for id, attr in self.mods.items():
123 attr['button'].set_sensitive( command.modify(attr['replace']) )
124
125 def enable_sourcesB(self):
126 single = self.composites_[self.composite].single()
127 self.frame_b.set_sensitive(not single)
128
129 def enable_sources(self):
130 for invalid_button in self.invalid_buttons:
131 invalid_button.set_sensitive(False)
132
133 def command(self):
134 # process all selected replactions
135 command = CompositeCommand(self.composite, self.sourceA, self.sourceB)
136 for id, attr in self.mods.items():
137 if self.modstates[id]:
138 command.modify(attr['replace'])
139 return command
140
141 def test(self):
142 if self.do_test:
143 if self.sourceA == self.sourceB:
144 return False
145 self.log.info("Testing transition to '%s'", str(self.command()))
146 Connection.send('best', str(self.command()))
147
148 def set_command(self, command, do_test=True):
149 self.do_test = do_test
150 self.log.info("Changing new composite to '%s'", str(self.command()))
151 if type(command) == str:
152 command = CompositeCommand.from_str(command)
153 for id, item in self.mods.items():
154 item['button'].set_active(
155 command.modify(item['replace'], reverse=True))
156 self.composites[command.composite]['button'].set_active(True)
157 self.sourcesA[command.A]['button'].set_active(True)
158 self.sourcesB[command.B]['button'].set_active(True)
159 self.test()
160 self.do_test = True
161
162 def on_best(self, best, targetA, targetB):
163 c = self.command()
164 if (c.A, c.B) != (targetA, targetB) and (c.A, c.B) != (targetB, targetA):
165 c.A = targetA
166 c.B = targetB
167 self.do_test = False
168 self.set_command(c)
169 self.do_test = True
170 self.update_glow()
171
172 def on_composite(self, command):
173 self.output = CompositeCommand.from_str(command)
174 self.test()
175
176 def update_glow(self):
177 output = copy.copy(self.output)
178 for id, item in self.sourcesA.items():
179 if id == output.A:
180 item['button'].get_style_context().add_class("glow")
181 else:
182 item['button'].get_style_context().remove_class("glow")
183 single = self.composites_[self.composite].single()
184 output_single = self.composites_[output.composite].single()
185 for id, item in self.sourcesB.items():
186 if id == output.B:
187 if output_single:
188 item['button'].get_style_context().remove_class("glow")
189 elif single:
190 self.sourcesA[id]['button'].get_style_context().add_class("glow")
191 item['button'].get_style_context().remove_class("glow")
192 else:
193 item['button'].get_style_context().add_class("glow")
194 else:
195 item['button'].get_style_context().remove_class("glow")
196 for id, item in self.mods.items():
197 if output.unmodify(item['replace']):
198 item['button'].get_style_context().add_class("glow")
199 else:
200 item['button'].get_style_context().remove_class("glow")
201 for id, item in self.composites.items():
202 if id == output.composite:
203 item['button'].get_style_context().add_class("glow")
204 else:
205 item['button'].get_style_context().remove_class("glow")
206
207 def validate(self,sources):
208 for id, attr in sources.items():
209 if id not in Config.getSources():
210 self.invalid_buttons.append(attr['button'])
+0
-113
voctogui/lib/toolbar/streamblank.py less more
0 import logging
1
2 from gi.repository import Gtk
3 import lib.connection as Connection
4
5 from lib.config import Config
6
7
8 class StreamblankToolbarController(object):
9 """Manages Accelerators and Clicks on the Composition Toolbar-Buttons"""
10
11 def __init__(self, toolbar, win, uibuilder, warning_overlay):
12 self.log = logging.getLogger('StreamblankToolbarController')
13
14 self.warning_overlay = warning_overlay
15
16 accelerators = Gtk.AccelGroup()
17 win.add_accel_group(accelerators)
18
19 livebtn = uibuilder.find_widget_recursive(toolbar, 'stream_live')
20 blankbtn = uibuilder.find_widget_recursive(toolbar, 'stream_blank')
21
22 blankbtn_pos = toolbar.get_item_index(blankbtn)
23
24 if not Config.getboolean('stream-blanker', 'enabled'):
25 self.log.info('disabling stream-blanker features '
26 'because the server does not support them: %s',
27 Config.getboolean('stream-blanker', 'enabled'))
28
29 toolbar.remove(livebtn)
30 toolbar.remove(blankbtn)
31 return
32
33 blank_sources = Config.getlist('stream-blanker', 'sources')
34
35 self.current_status = None
36
37 key, mod = Gtk.accelerator_parse('F12')
38 livebtn.connect('toggled', self.on_btn_toggled)
39 livebtn.set_name('live')
40 livebtn.set_label(livebtn.get_label() + "\nF12")
41
42 tooltip = Gtk.accelerator_get_label(key, mod)
43 livebtn.set_tooltip_text(tooltip)
44
45 livebtn.get_child().add_accelerator('clicked', accelerators,
46 key, mod, Gtk.AccelFlags.VISIBLE)
47
48 self.livebtn = livebtn
49 self.blank_btns = {}
50
51 accel_f_key = 11
52
53 for idx, name in enumerate(blank_sources):
54 key, mod = Gtk.accelerator_parse('F%u' % accel_f_key)
55
56 if idx == 0:
57 new_btn = blankbtn
58 else:
59 new_icon = Gtk.Image.new_from_pixbuf(blankbtn.get_icon_widget()
60 .get_pixbuf())
61 new_btn = Gtk.RadioToolButton(group=livebtn)
62 new_btn.set_icon_widget(new_icon)
63 toolbar.insert(new_btn, blankbtn_pos)
64
65 new_btn.set_label("Stream %s\nF%s" % (name, accel_f_key))
66 new_btn.connect('toggled', self.on_btn_toggled)
67 new_btn.set_name(name)
68
69 tooltip = Gtk.accelerator_get_label(key, mod)
70 new_btn.set_tooltip_text(tooltip)
71
72 new_btn.get_child().add_accelerator(
73 'clicked', accelerators,
74 key, mod, Gtk.AccelFlags.VISIBLE)
75
76 self.blank_btns[name] = new_btn
77 accel_f_key = accel_f_key - 1
78
79 # connect event-handler and request initial state
80 Connection.on('stream_status', self.on_stream_status)
81 Connection.send('get_stream_status')
82
83 def on_btn_toggled(self, btn):
84 if not btn.get_active():
85 return
86
87 btn_name = btn.get_name()
88 if btn_name == 'live':
89 self.warning_overlay.disable()
90
91 else:
92 self.warning_overlay.enable(btn_name)
93
94 if self.current_status == btn_name:
95 self.log.info('stream-status already activate: %s', btn_name)
96 return
97
98 self.log.info('stream-status activated: %s', btn_name)
99 if btn_name == 'live':
100 Connection.send('set_stream_live')
101 else:
102 Connection.send('set_stream_blank', btn_name)
103
104 def on_stream_status(self, status, source=None):
105 self.log.info('on_stream_status callback w/ status %s and source %s',
106 status, source)
107
108 self.current_status = source if source is not None else status
109 if status == 'live':
110 self.livebtn.set_active(True)
111 else:
112 self.blank_btns[source].set_active(True)
0 #!/usr/bin/env python3
1 from gi.repository import Gtk
2 import sys
3
4 def _decode(text, multiline=True):
5 ''' decode multiline text '''
6 if multiline:
7 text = text.replace('\\n', '\n')
8 else:
9 text = text.replace('\\n', ' ')
10 return text
11
12
13 class Widgets(dict):
14 ''' reads widget setup from configuration from INI file.
15 shall look like this:
16
17 myitem.name = My Item
18 myitem.key = <Shift>F1
19 myitem.tip = Some tooltip text
20
21 'myitem' will be the ID of the item used to identify the button.
22 'name' is an optional attribute which replaces the item ID in the button label
23 'tip' is an optional attribute which will be used for a tool tip message
24
25 additional some attributes will be added automatically:
26
27 'id' is a copy of the ID within the attributes
28 'button' is the created button instance
29
30 an extra member 'ids' becomes a list of all available IDs
31 '''
32
33 def __init__(self, cfg_items, listname="widgets"):
34 # read all config items with their attributes
35 self.ids = []
36 if cfg_items:
37 filter = cfg_items[listname].split(
38 ',') if listname in cfg_items else None
39 for cfg_name, cfg_value in cfg_items.items():
40 if cfg_name != listname:
41 id, attr = cfg_name.rsplit('.', 1)
42 if (filter is None) or id in filter:
43 if id not in self:
44 self.ids.append(id)
45 self[id] = dict()
46 self[id]['id'] = id
47 self[id][attr] = cfg_value
48
49 def add(self, widget, id, accelerators=None, callback=None, signal='clicked', css=[], sensitive=True, visible=True, multiline_names=True):
50 # set button properties
51 widget.set_can_focus(False)
52 widget.set_sensitive(sensitive)
53 widget.set_visible(visible)
54
55 # set button style class
56 context = widget.get_style_context()
57 for c in css:
58 context.add_class(c)
59
60 # set interaction callback
61 if callback:
62 widget.connect(signal, callback)
63
64 if id in self:
65 attr = self[id]
66
67 widget.set_name(id)
68
69 # set button label
70 if 'name' in attr:
71 name = _decode(attr['name'], multiline_names).upper()
72 else:
73 name = id.upper()
74 widget.set_label(name)
75
76 # set button tooltip
77 if 'tip' in attr:
78 tip = _decode(attr['tip'])
79 else:
80 tip = "Select source %s" % _decode(name, False)
81
82 # set accelerator key and tooltip
83 if accelerators and 'key' in attr:
84 key, mod = Gtk.accelerator_parse(attr['key'])
85 widget.set_tooltip_text(
86 "%s\nKey: '%s'" % (tip, attr['key'].upper()))
87 # @HACK: found no explanation why ToolItems must attach their
88 # accelerators to the child window
89 w = widget.get_child() if isinstance(widget,Gtk.ToolItem) else widget
90 w.add_accelerator(
91 'clicked', accelerators,
92 key, mod, Gtk.AccelFlags.VISIBLE)
93 else:
94 widget.set_tooltip_text(tip)
95
96 # set button tooltip
97 if 'expand' in attr:
98 widget.set_expand(True)
99
100 widget.set_can_focus(False)
55
66 from lib.videodisplay import VideoDisplay
77 from lib.audioleveldisplay import AudioLevelDisplay
8 from lib.warningoverlay import VideoWarningOverlay
8 from lib.audiodisplay import AudioDisplay
9 from lib.videopreviews import VideoPreviewsController
10 from lib.queues import QueuesWindowController
11 from lib.ports import PortsWindowController
912
10 from lib.videopreviews import VideoPreviewsController
11
12 from lib.toolbar.composition import CompositionToolbarController
13 from lib.toolbar.streamblank import StreamblankToolbarController
13 from lib.toolbar.mix import MixToolbarController
14 from lib.toolbar.preview import PreviewToolbarController
15 from lib.toolbar.overlay import OverlayToolbarController
16 from lib.toolbar.blinder import BlinderToolbarController
1417 from lib.toolbar.misc import MiscToolbarController
1518
16 from lib.shortcuts import show_shortcuts
19 from lib.studioclock import StudioClock
1720
18 from lib.studioclock import StudioClock
21 from vocto.port import Port
1922
2023
2124 class Ui(UiBuilder):
3134 self.win = self.get_check_widget('window')
3235
3336 # check for configuration option mainwindow/force_fullscreen
34 if Config.getboolean('mainwindow', 'forcefullscreen', fallback=False):
37 if Config.getForceFullScreen():
3538 self.log.info(
3639 'Forcing main window to full screen by configuration')
3740 # set window into fullscreen mode
3841 self.win.fullscreen()
3942 else:
4043 # check for configuration option mainwindow/width and /height
41 if Config.has_option('mainwindow', 'width') \
42 and Config.has_option('mainwindow', 'height'):
43 # get size from config
44 width = Config.getint('mainwindow', 'width')
45 height = Config.getint('mainwindow', 'height')
46 self.log.info(
47 'Set window size by configuration to %d:%d', width, height)
44 if Config.getWindowSize():
4845 # set window size
49 self.win.set_size_request(width, height)
46 self.win.set_size_request(*Config.getWindowSize())
5047 self.win.set_resizable(False)
5148
5249 # Connect Close-Handler
5350 self.win.connect('delete-event', Gtk.main_quit)
5451
55 # Get Audio-Level Display
56 self.audio_level_display = self.find_widget_recursive(
57 self.win, 'audiolevel_main')
58 # Create Main-Video Overlay Controller
59 drawing_area = self.find_widget_recursive(self.win,
60 'video_overlay_drawingarea')
61 self.video_warning_overlay = VideoWarningOverlay(drawing_area)
52 output_aspect_ratio = self.find_widget_recursive(
53 self.win, 'output_aspect_ratio')
54 output_aspect_ratio.props.ratio = Config.getVideoRatio()
55
56 audio_box = self.find_widget_recursive(self.win, 'audio_box')
57
58 # Setup Preview Controller
59 self.video_previews = VideoPreviewsController(
60 self.find_widget_recursive(self.win, 'preview_box'),
61 audio_box,
62 win=self.win,
63 uibuilder=self
64 )
65 if Config.getPreviewsEnabled():
66 for idx, source in enumerate(Config.getSources()):
67 self.video_previews.addPreview(self, source,
68 Port.SOURCES_PREVIEW + idx)
69 elif Config.getMirrorsEnabled():
70 for idx, source in enumerate(Config.getMirrorsSources()):
71 self.video_previews.addPreview(
72 self, source, Port.SOURCES_OUT + idx)
73 else:
74 self.log.warning(
75 'Can not show source previews because neither previews nor mirrors are enabled (see previews/enabled and mirrors/enabled in core configuration)')
76
77 self.mix_audio_display = AudioDisplay(audio_box, "mix", uibuilder=self)
6278
6379 # Create Main-Video Display
64 drawing_area = self.find_widget_recursive(self.win, 'video_main')
65 self.main_video_display = VideoDisplay(
66 drawing_area,
67 port=11000,
68 play_audio=Config.getboolean('mainvideo', 'playaudio'),
69 level_callback=self.audio_level_display.level_callback
80 self.mix_video_display = VideoDisplay(
81 self.find_widget_recursive(self.win, 'video_main'),
82 self.mix_audio_display,
83 port=Port.MIX_PREVIEW if Config.getPreviewsEnabled() else Port.MIX_OUT,
84 name="MIX"
7085 )
7186
72 # Setup Preview Controller
73 box_left = self.find_widget_recursive(self.win, 'box_left')
74 self.video_previews_controller = VideoPreviewsController(
75 box_left,
87 for idx, livepreview in enumerate(Config.getLivePreviews()):
88 if Config.getPreviewsEnabled():
89 self.video_previews.addPreview(
90 self, '{}-live'.format(livepreview), Port.LIVE_PREVIEW + idx, has_volume=False)
91 else:
92 self.video_previews.addPreview(
93 self, '{}-live'.format(livepreview), Port.LIVE_OUT + idx, has_volume=False)
94
95 self.preview_toolbar_controller = PreviewToolbarController(
7696 win=self.win,
7797 uibuilder=self
7898 )
7999
80 # Setup Toolbar Controllers
81 toolbar = self.find_widget_recursive(self.win, 'toolbar')
82 self.composition_toolbar_controller = CompositionToolbarController(
83 toolbar,
100 self.overlay_toolbar_controller = OverlayToolbarController(
84101 win=self.win,
85102 uibuilder=self
86103 )
87104
88 self.streamblank_toolbar_controller = StreamblankToolbarController(
89 toolbar,
105 self.mix_toolbar_controller = MixToolbarController(
90106 win=self.win,
91107 uibuilder=self,
92 warning_overlay=self.video_warning_overlay
108 preview_controller=self.preview_toolbar_controller,
109 overlay_controller=self.overlay_toolbar_controller
93110 )
94111
95 self.misc_controller = MiscToolbarController(
96 toolbar,
112 self.blinder_toolbar_controller = BlinderToolbarController(
97113 win=self.win,
98114 uibuilder=self
99115 )
100116
117 self.queues_controller = QueuesWindowController(self)
118 self.ports_controller = PortsWindowController(self)
119
120 self.misc_controller = MiscToolbarController(
121 win=self.win,
122 uibuilder=self,
123 queues_controller=self.queues_controller,
124 ports_controller=self.ports_controller,
125 video_display=self.mix_video_display
126 )
127
101128 # Setup Shortcuts window
102 self.win.connect('key-press-event', self.handle_keypress)
103129 self.win.connect('window-state-event', self.handle_state)
104
105 def handle_keypress(self, window, event):
106 if event.keyval == Gdk.KEY_question:
107 show_shortcuts(window)
108130
109131 def handle_state(self, window, event):
110132 # force full screen if whished by configuration
111 if Config.getboolean('mainwindow', 'forcefullscreen', fallback=False):
133 if Config.getForceFullScreen():
112134 self.log.info('re-forcing fullscreen mode')
113135 self.win.fullscreen()
114136
00 import logging
1 import re
2 from gi.repository import Gst
1 import sys
2
3 from gi.repository import Gst, Gdk
34
45 from lib.args import Args
56 from lib.config import Config
67 from lib.clock import Clock
78
9 from vocto.port import Port
10 from vocto.debug import gst_generate_dot
11 from vocto.pretty import pretty
12
813
914 class VideoDisplay(object):
1015 """Displays a Voctomix-Video-Stream into a GtkWidget"""
1116
12 def __init__(self, drawing_area, port, width=None, height=None,
13 play_audio=False, level_callback=None):
14 self.log = logging.getLogger('VideoDisplay[%u]' % port)
15
16 self.drawing_area = drawing_area
17 self.level_callback = level_callback
18
19 if Config.has_option('previews', 'videocaps'):
20 previewcaps = Config.get('previews', 'videocaps')
17 def __init__(self, video_drawing_area, audio_display, port, name, width=None, height=None,
18 play_audio=False):
19 self.log = logging.getLogger('VideoDisplay:%s' % name)
20 self.name = name
21 self.video_drawing_area = video_drawing_area
22 self.level_callback = audio_display.callback
23 video_decoder = None
24
25 # Setup Server-Connection, Demuxing and Decoding
26 pipe = """
27 tcpclientsrc
28 name=tcpsrc-{name}
29 host={host}
30 port={port}
31 blocksize=1048576
32 ! matroskademux
33 name=demux-{name}
34 """.format(name=name,
35 host=Config.getHost(),
36 port=port)
37
38 if Config.getPreviewsEnabled():
39 self.log.info('using encoded previews instead of raw-video')
40 if Config.getPreviewVaapi():
41 if Gst.version() < (1, 8):
42 vaapi_decoders = {
43 'h264': 'vaapidecode_h264',
44 'mpeg2': 'vaapidecode_mpeg2',
45 }
46 else:
47 vaapi_decoders = {
48 'h264': 'vaapih264dec',
49 'mpeg2': 'vaapimpeg2dec',
50 }
51
52 video_decoder = vaapi_decoders[Config.getPreviewDecoder()]
53 else:
54 cpu_decoders = {
55 'h264': 'video/x-h264\n! avdec_h264',
56 'jpeg': 'image/jpeg\n! jpegdec',
57 'mpeg2': 'video/mpeg\nmpegversion=2\n! mpeg2dec'
58 }
59
60 video_decoder = cpu_decoders[Config.getPreviewDecoder()]
61
62 pipe += """
63 demux-{name}.
64 ! queue
65 name=queue-video-{name}
66 ! {video_decoder}
67 """.format(name=name,
68 video_decoder=video_decoder)
69
2170 else:
22 previewcaps = Config.get('mix', 'videocaps')
23
24 use_previews = Config.getboolean('previews', 'enabled') \
25 and Config.getboolean('previews', 'use')
26
27 audiostreams = int(Config.get('mix', 'audiostreams'))
28
29 if (Config.get('mainvideo', 'vumeter') != 'all') \
30 and int(Config.get('mainvideo', 'vumeter')) < audiostreams:
31 audiostreams = int(Config.get('mainvideo', 'vumeter'))
32
33 # Preview-Ports are Raw-Ports + 1000
34 if use_previews:
35 self.log.info('using encoded previews instead of raw-video')
36 port += 1000
37
38 vdec = 'image/jpeg ! jpegdec'
39 if Config.has_option('previews', 'vaapi'):
40 try:
41 decoder = Config.get('previews', 'vaapi')
42 decoders = {
43 'h264': 'video/x-h264 ! avdec_h264',
44 'jpeg': 'image/jpeg ! jpegdec',
45 'mpeg2': 'video/mpeg,mpegversion=2 ! mpeg2dec'
46 }
47 vdec = decoders[decoder]
48 except Exception as e:
49 self.log.error(e)
50
51 else:
71 video_decoder = None
72 preview_caps = 'video/x-raw'
5273 self.log.info('using raw-video instead of encoded-previews')
53 vdec = None
54
55 # Setup Server-Connection, Demuxing and Decoding
56 pipeline = """
57 tcpclientsrc host={host} port={port} blocksize=1048576 !
58 queue !
59 matroskademux name=demux
60 """
61
62 if use_previews:
63 pipeline += """
64 demux. !
65 {vdec} !
66 {previewcaps} !
67 queue !
68 """
69
70 else:
71 pipeline += """
72 demux. !
73 {vcaps} !
74 queue !
75 """
74 pipe += """
75 demux-{name}.
76 ! queue
77 name=queue-video-{name}
78 ! {previewcaps}
79 """.format(name=name,
80 previewcaps=preview_caps,
81 vcaps=Config.getVideoCaps())
82
83 pipe += """ ! videoconvert
84 ! videoscale
85 """
86
87 if Config.getPreviewNameOverlay() and name:
88 pipe += """\
89 ! textoverlay
90 name=title-{name}
91 text=\"{name}\"
92 valignment=bottom
93 halignment=center
94 shaded-background=yes
95 font-desc="Roboto, 22"
96 """.format(name=name)
7697
7798 # Video Display
78 videosystem = Config.get('videodisplay', 'system')
99 videosystem = Config.getVideoSystem()
79100 self.log.debug('Configuring for Video-System %s', videosystem)
101
80102 if videosystem == 'gl':
81 pipeline += """
82 glupload !
83 glcolorconvert !
84 glimagesinkelement
85 """
103 pipe += """ ! glupload
104 ! glcolorconvert
105 ! glimagesinkelement
106 name=imagesink-{name}
107 """.format(name=name)
86108
87109 elif videosystem == 'xv':
88 pipeline += """
89 xvimagesink
90 """
110 pipe += """ ! xvimagesink
111 name=imagesink-{name}
112 """.format(name=name)
91113
92114 elif videosystem == 'x':
93 prescale_caps = 'video/x-raw'
94 if width and height:
95 prescale_caps += ',width=%u,height=%u' % (width, height)
96
97 pipeline += """
98 videoconvert !
99 videoscale !
100 {prescale_caps} !
101 ximagesink
102 """.format(prescale_caps=prescale_caps)
115 pipe += """ ! ximagesink
116 name=imagesink-{name}
117 """.format(name=name)
118
119 elif videosystem == 'vaapi':
120 pipe += """ ! vaapisink
121 name=imagesink-{name}
122 """.format(name=name)
103123
104124 else:
105125 raise Exception(
106126 'Invalid Videodisplay-System configured: %s' % videosystem
107127 )
108128
109 # If an Audio-Path is required,
110129 # add an Audio-Path through a level-Element
111 if self.level_callback or play_audio:
112 for audiostream in range(0, audiostreams):
113 pipeline += """
114 demux.audio_{audiostream} !
115 """.format(audiostream=audiostream)
116 pipeline += """
117 {acaps} !
118 queue !
119 """
120 pipeline += """
121 level name=lvl_{audiostream} interval=50000000 !
122 """.format(audiostream=audiostream)
123
124 # If Playback is requested, push fo pulseaudio
125 if play_audio and audiostream == 0:
126 pipeline += """
127 pulsesink
128 """
129
130 # Otherwise just trash the Audio
131 else:
132 pipeline += """
133 fakesink
134 """
135
136 pipeline = pipeline.format(
137 acaps=Config.get('mix', 'audiocaps'),
138 vcaps=Config.get('mix', 'videocaps'),
139 previewcaps=previewcaps,
140 host=Args.host if Args.host else Config.get('server', 'host'),
141 vdec=vdec,
142 port=port,
143 )
144
145 self.log.debug('Creating Display-Pipeline:\n%s', pipeline)
146 self.pipeline = Gst.parse_launch(pipeline)
130 pipe += """
131 demux-{name}.
132 ! queue
133 name=queue-audio-{name}
134 ! level
135 name=lvl
136 interval=50000000
137 ! audioconvert
138 """
139
140 # If Playback is requested, push fo pulseaudio
141 if play_audio:
142 pipe += """ ! pulsesink
143 name=audiosink-{name}
144 """
145 else:
146 pipe += """ ! fakesink
147 """
148 pipe = pipe.format(name=name,
149 acaps=Config.getAudioCaps(),
150 port=port,
151 )
152
153 self.log.info("Creating Display-Pipeline:\n%s", pretty(pipe))
154 try:
155 # launch gstreamer pipeline
156 self.pipeline = Gst.parse_launch(pipe)
157 self.log.info("pipeline launched successfuly")
158 except:
159 self.log.error("Can not launch pipeline")
160 sys.exit(-1)
161
162 if Args.dot:
163 self.log.debug("Generating DOT image of videodisplay pipeline")
164 gst_generate_dot(self.pipeline, "gui.videodisplay.{}".format(name))
165
147166 self.pipeline.use_clock(Clock)
148167
149 self.drawing_area.realize()
150 self.xid = self.drawing_area.get_property('window').get_xid()
151 self.log.debug('Realized Drawing-Area with xid %u', self.xid)
152
168 self.video_drawing_area.add_events(
169 Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK)
170 self.video_drawing_area.connect("realize", self.on_realize)
153171 bus = self.pipeline.get_bus()
154172 bus.add_signal_watch()
155173 bus.enable_sync_message_emission()
156174
157175 bus.connect('message::error', self.on_error)
158 bus.connect("sync-message::element", self.on_syncmsg)
159
160 if self.level_callback:
161 bus.connect("message::element", self.on_level)
162
163 self.log.debug('Launching Display-Pipeline')
176 bus.connect('sync-message::element', self.on_syncmsg)
177 bus.connect('message::state-changed', self.on_state_changed)
178 bus.connect("message::element", self.on_level)
179
180 def on_realize(self, win):
181 self.imagesink = self.pipeline.get_by_name(
182 'imagesink-{name}'.format(name=self.name))
183 self.xid = self.video_drawing_area.get_property('window').get_xid()
184
185 self.log.debug('Realized Drawing-Area with xid %u', self.xid)
186 self.video_drawing_area.realize()
187
188 self.log.info("Launching Display-Pipeline")
164189 self.pipeline.set_state(Gst.State.PLAYING)
165190
166191 def on_syncmsg(self, bus, msg):
167 if msg.get_structure().get_name() == "prepare-window-handle":
168 self.log.info('Setting imagesink window-handle to %s', self.xid)
169 msg.src.set_window_handle(self.xid)
192 if type(msg) == Gst.Message and self.imagesink:
193 if msg.get_structure().get_name() == "prepare-window-handle":
194 self.log.info(
195 'Setting imagesink window-handle to 0x%x', self.xid)
196 self.imagesink.set_window_handle(self.xid)
170197
171198 def on_error(self, bus, message):
172 self.log.debug('Received Error-Signal on Display-Pipeline')
173199 (error, debug) = message.parse_error()
174 self.log.debug('Error-Details: #%u: %s', error.code, debug)
200 self.log.error(
201 "GStreamer pipeline element '%s' signaled an error #%u: %s" % (message.src.name, error.code, error.message))
202
203 def mute(self, mute):
204 self.pipeline.get_by_name("audiosink-{name}".format(name=self.name)).set_property(
205 "volume", 1 if mute else 0)
175206
176207 def on_level(self, bus, msg):
177 if not(msg.src.name.startswith('lvl_')):
178 return
179
180 if msg.type != Gst.MessageType.ELEMENT:
181 return
182
183 rms = msg.get_structure().get_value('rms')
184 peak = msg.get_structure().get_value('peak')
185 decay = msg.get_structure().get_value('decay')
186 stream = int(msg.src.name[4])
187 self.level_callback(rms, peak, decay, stream)
208 if self.level_callback and msg.src.name == 'lvl':
209 rms = msg.get_structure().get_value('rms')
210 peak = msg.get_structure().get_value('peak')
211 decay = msg.get_structure().get_value('decay')
212 self.level_callback(rms, peak, decay)
213
214 def on_state_changed(self, bus, message):
215 if message.parse_state_changed().newstate == Gst.State.PLAYING:
216 self.video_drawing_area.show()
0 #!/usr/bin/env python3
01 import logging
12 import json
23 import math
34 import os
45 from configparser import NoOptionError
56
6 from gi.repository import Gtk, GObject
7 from gi.repository import Gtk, Gdk, GObject
78 from lib.videodisplay import VideoDisplay
9 from lib.audiodisplay import AudioDisplay
810 import lib.connection as Connection
911
1012 from lib.config import Config
13 from vocto.port import Port
1114
1215
1316 class VideoPreviewsController(object):
1417 """Displays Video-Previews and selection Buttons for them"""
1518
16 def __init__(self, preview_box, win, uibuilder):
19 def __init__(self, video_box, audio_box, win, uibuilder):
1720 self.log = logging.getLogger('VideoPreviewsController')
1821
19 self.preview_box = preview_box
2022 self.win = win
2123
22 self.sources = Config.getlist('mix', 'sources')
2324 self.preview_players = {}
2425 self.previews = {}
25 self.a_btns = {}
26 self.b_btns = {}
2726 self.volume_sliders = {}
28
29 self.current_source = {'a': None, 'b': None}
30
31 try:
32 width = Config.getint('previews', 'width')
33 self.log.debug('Preview-Width configured to %u', width)
34 except NoOptionError:
35 width = 320
36 self.log.debug('Preview-Width selected as %u', width)
37
38 try:
39 height = Config.getint('previews', 'height')
40 self.log.debug('Preview-Height configured to %u', height)
41 except NoOptionError:
42 height = int(width * 9 / 16)
43 self.log.debug('Preview-Height calculated to %u', height)
27 self.video_box = video_box
28 self.audio_box = audio_box
4429
4530 # Accelerators
4631 accelerators = Gtk.AccelGroup()
4732 win.add_accel_group(accelerators)
4833
49 group_a = None
50 group_b = None
34 # count number of previews
35 num_previews = len(Config.getSources()) + len(Config.getLivePreviews())
5136
52 # Check if there is a fixed audio source configured.
53 # If so, we will remove the volume sliders entirely
54 # instead of setting them up.
55 volume_control = \
56 Config.getboolean('audio', 'volumecontrol', fallback=True) or \
57 Config.getboolean('audio', 'forcevolumecontrol', fallback=False)
37 # get preview size
38 self.previewSize = Config.getPreviewSize()
5839
59 for idx, source in enumerate(self.sources):
60 self.log.info('Initializing Video Preview %s', source)
61
62 preview = uibuilder.load_check_widget(
63 'widget_preview',
64 os.path.dirname(uibuilder.uifile) + "/widgetpreview.ui")
65 video = uibuilder.find_widget_recursive(preview, 'video')
66
67 video.set_size_request(width, height)
68 preview_box.pack_start(preview, fill=False,
69 expand=False, padding=0)
70
71 player = VideoDisplay(video, port=13000 + idx,
72 width=width, height=height)
73
74 uibuilder.find_widget_recursive(preview, 'label').set_label(source)
75 btn_a = uibuilder.find_widget_recursive(preview, 'btn_a')
76 btn_b = uibuilder.find_widget_recursive(preview, 'btn_b')
77
78 btn_a.set_name("%c %u" % ('a', idx))
79 btn_b.set_name("%c %u" % ('b', idx))
80
81 if not group_a:
82 group_a = btn_a
83 else:
84 btn_a.join_group(group_a)
85
86 if not group_b:
87 group_b = btn_b
88 else:
89 btn_b.join_group(group_b)
90
91 btn_a.connect('toggled', self.btn_toggled)
92 btn_b.connect('toggled', self.btn_toggled)
93
94 key, mod = Gtk.accelerator_parse('%u' % (idx + 1))
95 btn_a.add_accelerator('activate', accelerators,
96 key, mod, Gtk.AccelFlags.VISIBLE)
97 tooltip = Gtk.accelerator_get_label(key, mod)
98 btn_a.set_tooltip_text(tooltip)
99
100 key, mod = Gtk.accelerator_parse('<Ctrl>%u' % (idx + 1))
101 btn_b.add_accelerator('activate', accelerators,
102 key, mod, Gtk.AccelFlags.VISIBLE)
103 tooltip = Gtk.accelerator_get_label(key, mod)
104 btn_b.set_tooltip_text(tooltip)
105
106 volume_slider = uibuilder.find_widget_recursive(preview,
107 'audio_level')
108
109 if not volume_control:
110 box = uibuilder.find_widget_recursive(preview, 'box')
111 box.remove(volume_slider)
112 else:
113 volume_slider.set_name("volume {}".format(source))
114 volume_signal = volume_slider.connect('value-changed',
115 self.slider_changed)
116
117 def slider_format(scale, value):
118 if value == -20.0:
119 return "-\u221e\u202fdB"
120 else:
121 return "{:.{}f}\u202fdB".format(value,
122 scale.get_digits())
123
124 volume_slider.connect('format-value', slider_format)
125 self.volume_sliders[source] = (volume_slider, volume_signal)
126
127 self.preview_players[source] = player
128 self.previews[source] = preview
129 self.a_btns[source] = btn_a
130 self.b_btns[source] = btn_b
40 # recalculate preview size if in sum they are too large for screen
41 screen = Gdk.Screen.get_default()
42 if screen.get_height() < self.previewSize[1] * num_previews:
43 height = screen.get_height() / num_previews
44 self.previewSize = (Config.getVideoRatio() * height, height)
45 self.log.warning(
46 'Resizing previews so that they fit onto screen to WxH={}x{}'.format(*self.previewSize))
13147
13248 # connect event-handler and request initial state
133 Connection.on('video_status', self.on_video_status)
13449 Connection.send('get_video')
13550
136 if volume_control:
137 Connection.on('audio_status', self.on_audio_status)
138 Connection.send('get_audio')
51 def addPreview(self, uibuilder, source, port, has_volume=True):
13952
140 def btn_toggled(self, btn):
141 if not btn.get_active():
142 return
53 self.log.info('Initializing video preview %s at port %d', source, port)
14354
144 btn_name = btn.get_name()
145 self.log.debug('btn_toggled: %s', btn_name)
55 video = uibuilder.load_check_widget('video',
56 os.path.dirname(uibuilder.uifile) +
57 "/widgetpreview.ui")
58 video.set_size_request(*self.previewSize)
59 self.video_box.pack_start(video, fill=False,
60 expand=False, padding=0)
14661
147 channel, idx = btn_name.split(' ')[:2]
148 source_name = self.sources[int(idx)]
149
150 if self.current_source[channel] == source_name:
151 self.log.info('video-channel %s already on %s',
152 channel, source_name)
153 return
154
155 self.log.info('video-channel %s changed to %s', channel, source_name)
156 Connection.send('set_video_' + channel, source_name)
157
158 def slider_changed(self, slider):
159 slider_name = slider.get_name()
160 source = slider_name.split(' ')[1]
161 value = slider.get_value()
162 volume = 10 ** (value / 20) if value > -20.0 else 0
163 self.log.debug("slider_changed: {}: {:.4f}".format(source, volume))
164 Connection.send('set_audio_volume {} {:.4f}'.format(source, volume))
165
166 def on_video_status(self, source_a, source_b):
167 self.log.info('on_video_status callback w/ sources: %s and %s',
168 source_a, source_b)
169
170 self.current_source['a'] = source_a
171 self.current_source['b'] = source_b
172
173 self.a_btns[source_a].set_active(True)
174 self.b_btns[source_b].set_active(True)
175
176 def on_audio_status(self, *volumes):
177 volumes_json = "".join(volumes)
178 volumes = json.loads(volumes_json)
179
180 for source, volume in volumes.items():
181 volume = 20.0 * math.log10(volume) if volume > 0 else -20.0
182 slider, signal = self.volume_sliders[source]
183 # Temporarily block the 'value-changed' signal,
184 # so we don't (re)trigger it when receiving (our) changes
185 GObject.signal_handler_block(slider, signal)
186 slider.set_value(volume)
187 GObject.signal_handler_unblock(slider, signal)
62 mix_audio_display = AudioDisplay(self.audio_box, source, uibuilder, has_volume)
63 player = VideoDisplay(video, mix_audio_display, port=port,
64 width=self.previewSize[0],
65 height=self.previewSize[1],
66 name=source.upper()
67 )
+0
-61
voctogui/lib/warningoverlay.py less more
0 import logging
1 from gi.repository import GLib
2
3
4 class VideoWarningOverlay(object):
5 """Displays a Warning-Overlay above the Video-Feed
6 of another VideoDisplay"""
7
8 def __init__(self, drawing_area):
9 self.log = logging.getLogger('VideoWarningOverlay')
10
11 self.drawing_area = drawing_area
12 self.drawing_area.connect("draw", self.draw_callback)
13
14 self.text = None
15 self.blink_state = False
16
17 GLib.timeout_add_seconds(1, self.on_blink_callback)
18
19 def on_blink_callback(self):
20 self.blink_state = not self.blink_state
21 self.drawing_area.queue_draw()
22 return True
23
24 def enable(self, text=None):
25 self.text = text
26 self.drawing_area.show()
27 self.drawing_area.queue_draw()
28
29 def set_text(self, text=None):
30 self.text = text
31 self.drawing_area.queue_draw()
32
33 def disable(self):
34 self.drawing_area.hide()
35 self.drawing_area.queue_draw()
36
37 def draw_callback(self, drawing_area, cr):
38 w = drawing_area.get_allocated_width()
39 h = drawing_area.get_allocated_height()
40
41 if self.blink_state:
42 cr.set_source_rgba(1.0, 0.0, 0.0, 0.8)
43 else:
44 cr.set_source_rgba(1.0, 0.5, 0.0, 0.8)
45
46 cr.rectangle(0, 0, w, h)
47 cr.fill()
48
49 text = "Stream is Blanked"
50 if self.text:
51 text += ": " + self.text
52
53 cr.set_font_size(h * 0.75)
54 (xbearing, ybearing,
55 txtwidth, txtheight,
56 xadvance, yadvance) = cr.text_extents(text)
57
58 cr.move_to(w / 2 - txtwidth / 2, h * 0.75)
59 cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
60 cr.show_text(text)
0 <?xml version="1.0" encoding="UTF-8"?>
1 <!-- Generated with glade 3.22.1 -->
2 <interface>
3 <requires lib="gtk+" version="3.20"/>
4 <object class="GtkImage" id="Monitor">
5 <property name="visible">True</property>
6 <property name="can_focus">False</property>
7 <property name="pixbuf">headphones.svg</property>
8 </object>
9 <object class="GtkImage" id="Mute">
10 <property name="visible">True</property>
11 <property name="can_focus">False</property>
12 <property name="tooltip_text" translatable="yes">Mute</property>
13 <property name="pixbuf">mute.svg</property>
14 <property name="icon_size">3</property>
15 </object>
16 <object class="GtkAdjustment" id="audio_level_adjustment">
17 <property name="lower">-20</property>
18 <property name="upper">10</property>
19 <property name="step_increment">0.10000000000000001</property>
20 </object>
21 <object class="GtkFrame" id="audio">
22 <property name="visible">True</property>
23 <property name="can_focus">False</property>
24 <property name="margin_bottom">6</property>
25 <property name="hexpand">True</property>
26 <property name="label_xalign">0.5</property>
27 <property name="shadow_type">in</property>
28 <child>
29 <object class="GtkBox">
30 <property name="visible">True</property>
31 <property name="can_focus">False</property>
32 <property name="orientation">vertical</property>
33 <child>
34 <object class="GtkBox">
35 <property name="visible">True</property>
36 <property name="can_focus">False</property>
37 <property name="margin_top">6</property>
38 <child>
39 <object class="GtkLabel">
40 <property name="visible">True</property>
41 <property name="can_focus">False</property>
42 </object>
43 <packing>
44 <property name="expand">True</property>
45 <property name="fill">True</property>
46 <property name="position">0</property>
47 </packing>
48 </child>
49 <child>
50 <object class="AudioLevelDisplay" id="audio_level_display">
51 <property name="width_request">20</property>
52 <property name="visible">True</property>
53 <property name="can_focus">False</property>
54 <property name="margin_right">0</property>
55 <property name="margin_top">0</property>
56 <property name="margin_bottom">0</property>
57 </object>
58 <packing>
59 <property name="expand">True</property>
60 <property name="fill">True</property>
61 <property name="position">1</property>
62 </packing>
63 </child>
64 <child>
65 <object class="GtkScale" id="audio_level">
66 <property name="width_request">42</property>
67 <property name="visible">True</property>
68 <property name="can_focus">True</property>
69 <property name="orientation">vertical</property>
70 <property name="adjustment">audio_level_adjustment</property>
71 <property name="inverted">True</property>
72 <property name="restrict_to_fill_level">False</property>
73 <property name="fill_level">0</property>
74 <property name="round_digits">1</property>
75 </object>
76 <packing>
77 <property name="expand">False</property>
78 <property name="fill">True</property>
79 <property name="position">2</property>
80 </packing>
81 </child>
82 <child>
83 <object class="GtkLabel">
84 <property name="visible">True</property>
85 <property name="can_focus">False</property>
86 </object>
87 <packing>
88 <property name="expand">True</property>
89 <property name="fill">True</property>
90 <property name="position">3</property>
91 </packing>
92 </child>
93 </object>
94 <packing>
95 <property name="expand">True</property>
96 <property name="fill">True</property>
97 <property name="position">0</property>
98 </packing>
99 </child>
100 <child>
101 <object class="GtkBox">
102 <property name="visible">True</property>
103 <property name="can_focus">False</property>
104 <child>
105 <object class="GtkLabel">
106 <property name="visible">True</property>
107 <property name="can_focus">False</property>
108 </object>
109 <packing>
110 <property name="expand">True</property>
111 <property name="fill">True</property>
112 <property name="position">0</property>
113 </packing>
114 </child>
115 <child>
116 <object class="GtkToggleButton" id="monitor_button">
117 <property name="can_focus">False</property>
118 <property name="focus_on_click">False</property>
119 <property name="receives_default">True</property>
120 <property name="no_show_all">True</property>
121 <property name="tooltip_text" translatable="yes">Monitor these channels</property>
122 <property name="image">Monitor</property>
123 </object>
124 <packing>
125 <property name="expand">False</property>
126 <property name="fill">True</property>
127 <property name="position">1</property>
128 </packing>
129 </child>
130 <child>
131 <object class="GtkLabel">
132 <property name="visible">True</property>
133 <property name="can_focus">False</property>
134 <property name="no_show_all">True</property>
135 </object>
136 <packing>
137 <property name="expand">True</property>
138 <property name="fill">True</property>
139 <property name="position">2</property>
140 </packing>
141 </child>
142 <child>
143 <object class="GtkToggleButton" id="mute_button">
144 <property name="can_focus">False</property>
145 <property name="focus_on_click">False</property>
146 <property name="receives_default">True</property>
147 <property name="no_show_all">True</property>
148 <property name="tooltip_text" translatable="yes">Mute</property>
149 <property name="image">Mute</property>
150 </object>
151 <packing>
152 <property name="expand">False</property>
153 <property name="fill">True</property>
154 <property name="position">3</property>
155 </packing>
156 </child>
157 </object>
158 <packing>
159 <property name="expand">False</property>
160 <property name="fill">True</property>
161 <property name="position">1</property>
162 </packing>
163 </child>
164 </object>
165 </child>
166 <child type="label">
167 <object class="GtkLabel" id="audio_label">
168 <property name="visible">True</property>
169 <property name="can_focus">False</property>
170 <property name="label" translatable="yes">ORIGINAL</property>
171 </object>
172 </child>
173 </object>
174 </interface>
88 xmlns="http://www.w3.org/2000/svg"
99 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
1010 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="32"
11 width="64"
12 height="48"
1313 id="svg3052"
1414 version="1.1"
15 inkscape:version="0.48.5 r10040"
15 inkscape:version="0.92.3 (2405546, 2018-03-11)"
1616 sodipodi:docname="blank-stream.svg">
1717 <defs
1818 id="defs3054" />
2323 borderopacity="1.0"
2424 inkscape:pageopacity="0.0"
2525 inkscape:pageshadow="2"
26 inkscape:zoom="22.4"
27 inkscape:cx="9.4155359"
28 inkscape:cy="15.895534"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-41.27408"
28 inkscape:cy="-8.7808317"
2929 inkscape:document-units="px"
3030 inkscape:current-layer="layer1"
3131 showgrid="false"
32 inkscape:window-width="1920"
33 inkscape:window-height="1014"
32 inkscape:window-width="1912"
33 inkscape:window-height="1171"
3434 inkscape:window-x="0"
35 inkscape:window-y="27"
36 inkscape:window-maximized="1" />
35 inkscape:window-y="0"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
3741 <metadata
3842 id="metadata3057">
3943 <rdf:RDF>
5054 inkscape:label="Layer 1"
5155 inkscape:groupmode="layer"
5256 id="layer1"
53 transform="translate(0,-1020.3622)">
54 <rect
55 style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ff0000;stroke-width:0.50000000000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
56 id="rect3062"
57 width="30"
58 height="16.875"
59 x="1"
60 y="1027.9247" />
61 <path
62 style="fill:none;stroke:#ff0000;stroke-width:0.95493239;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
63 d="m 1.4349426,1028.3665 29.2343654,16.2057"
64 id="path4315"
65 inkscape:connector-curvature="0"
66 sodipodi:nodetypes="cc" />
67 <path
68 style="fill:none;stroke:#ff0000;stroke-width:0.95493239;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
69 d="M 1.5202044,1044.3957 30.529344,1028.3178"
70 id="path4335"
71 inkscape:connector-curvature="0"
72 sodipodi:nodetypes="cc" />
57 transform="translate(0,-1004.3622)">
58 <g
59 id="g8261"
60 transform="matrix(0.33855306,0,0,0.33855306,-158.80711,918.41064)">
61 <g
62 transform="translate(206.81577,216.25459)"
63 id="g8240"
64 style="fill:#000000">
65 <rect
66 style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
67 id="rect8242"
68 width="162.79439"
69 height="116.67266"
70 x="275.3829"
71 y="50.17815"
72 ry="19.285713"
73 rx="19.285715" />
74 </g>
75 <g
76 id="g8210"
77 transform="matrix(0.78282213,0,0,1,423.47741,218.19296)" />
78 <g
79 id="g8222"
80 transform="matrix(0.45187084,0,0,0.45187084,457.78898,297.34173)" />
81 </g>
82 <g
83 transform="matrix(0.33855306,0,0,0.33855306,-158.80711,918.41064)"
84 id="g18395"
85 style="fill:none">
86 <g
87 style="fill:none"
88 id="g18397"
89 transform="translate(206.81577,216.25459)">
90 <rect
91 rx="19.285715"
92 ry="19.285713"
93 y="50.17815"
94 x="275.3829"
95 height="116.67266"
96 width="162.79439"
97 id="rect18399"
98 style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
99 </g>
100 <g
101 transform="matrix(0.78282213,0,0,1,423.47741,218.19296)"
102 id="g18401"
103 style="fill:none" />
104 <g
105 transform="matrix(0.45187084,0,0,0.45187084,457.78898,297.34173)"
106 id="g18403"
107 style="fill:none" />
108 </g>
109 <g
110 id="g18452"
111 transform="matrix(1.9041916,0,0,1.9041916,2.711969,-945.99225)"
112 style="fill:#ff4500">
113 <rect
114 ry="0"
115 rx="0"
116 y="1031.2018"
117 x="9.1208811"
118 height="11.289488"
119 width="4.6332374"
120 id="rect18448"
121 style="opacity:1;fill:#ff4500;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.4917624;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
122 <rect
123 style="opacity:1;fill:#ff4500;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.4917624;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
124 id="rect18450"
125 width="4.6332374"
126 height="11.289488"
127 x="17.007524"
128 y="1031.2018"
129 rx="0"
130 ry="0" />
131 </g>
73132 </g>
74133 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="clock.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-18.368791"
28 inkscape:cy="-8.7808317"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1172"
34 inkscape:window-x="1920"
35 inkscape:window-y="0"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 id="g26166"
60 transform="translate(71.858806,41.649805)">
61 <g
62 style="fill:#000000"
63 id="g8232"
64 transform="matrix(0.33818321,0,0,0.33818321,-160.47192,950.01462)">
65 <rect
66 rx="19.285715"
67 ry="19.285713"
68 y="50.17815"
69 x="275.3829"
70 height="116.67266"
71 width="162.79439"
72 id="rect8234"
73 style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
74 </g>
75 <g
76 transform="matrix(0.16891997,0,0,0.16891997,-51.998466,968.66124)"
77 id="g8184">
78 <rect
79 rx="19.285715"
80 ry="19.285713"
81 y="48.52586"
82 x="75.449371"
83 height="116.67266"
84 width="162.79439"
85 id="rect8186"
86 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
87 <path
88 inkscape:connector-curvature="0"
89 id="path8188"
90 d="m 91.037227,71.671452 107.225493,0"
91 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
92 <path
93 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
94 d="m 91.037227,88.833834 63.768463,0"
95 id="path8190"
96 inkscape:connector-curvature="0" />
97 <path
98 inkscape:connector-curvature="0"
99 id="path8192"
100 d="m 91.037227,105.99619 63.768463,0"
101 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
102 <path
103 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
104 d="m 91.037227,123.15858 63.768463,0"
105 id="path8194"
106 inkscape:connector-curvature="0" />
107 <rect
108 ry="0"
109 rx="0"
110 y="93.87233"
111 x="165.66994"
112 height="49.597694"
113 width="12.753692"
114 id="rect8196"
115 style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
116 <rect
117 style="opacity:1;fill:#228b22;fill-opacity:1;fill-rule:evenodd;stroke:#228b22;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
118 id="rect8198"
119 width="12.753692"
120 height="31.175694"
121 x="188.34315"
122 y="112.29432"
123 rx="0"
124 ry="0" />
125 <rect
126 ry="0"
127 rx="0"
128 y="101.43008"
129 x="211.0164"
130 height="42.039951"
131 width="12.753692"
132 id="rect8200"
133 style="opacity:1;fill:#4169e1;fill-opacity:1;fill-rule:evenodd;stroke:#4169e1;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
134 </g>
135 <g
136 transform="matrix(0.16891997,0,0,0.16891997,-114.25311,968.38213)"
137 id="g8202">
138 <rect
139 style="opacity:1;fill:#dcdcdc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
140 id="rect8204"
141 width="162.79439"
142 height="116.67266"
143 x="273.87848"
144 y="50.17815"
145 ry="19.285713"
146 rx="19.285715" />
147 <path
148 inkscape:connector-curvature="0"
149 id="path8206"
150 d="m 405.90317,166.85081 0,-11.80915 c 0,-1.76639 -0.17797,-3.48877 -0.51665,-5.15223 -0.33866,-1.66346 -0.8378,-3.26758 -1.48147,-4.79444 -0.64366,-1.52685 -1.43187,-2.97676 -2.34685,-4.33334 -0.91498,-1.35657 -1.95757,-2.62016 -3.11018,-3.77278 -1.15262,-1.15262 -2.41492,-2.19392 -3.7715,-3.1089 -1.35657,-0.91497 -2.80777,-1.70318 -4.33463,-2.34685 -1.52686,-0.64366 -3.12969,-1.1428 -4.79315,-1.48147 -1.66346,-0.33867 -3.38712,-0.51664 -5.15351,-0.51664 l -47.23535,0 c -1.76638,0 -3.49005,0.17797 -5.1535,0.51664 -1.66346,0.33867 -3.26759,0.83781 -4.79445,1.48147 -1.52686,0.64367 -2.97676,1.43188 -4.33334,2.34685 -1.35658,0.91498 -2.61888,1.95628 -3.77149,3.1089 -1.15262,1.15262 -2.19521,2.41621 -3.11019,3.77278 -0.91498,1.35658 -1.70318,2.80649 -2.34685,4.33334 -0.64366,1.52686 -1.1428,3.13098 -1.48147,4.79444 -0.33867,1.66346 -0.51664,3.38584 -0.51664,5.15223 l 0,11.80915 98.25122,0 z"
151 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
152 <circle
153 r="22.437052"
154 cy="99.067307"
155 cx="356.06894"
156 id="circle8208"
157 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
158 </g>
159 </g>
160 </g>
161 </svg>
+0
-63
voctogui/ui/composite-fullscreen.svg less more
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="32"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.48.5 r10040"
16 sodipodi:docname="blank-stream.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="22.4"
27 inkscape:cx="17.339643"
28 inkscape:cy="15.895534"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1920"
33 inkscape:window-height="1014"
34 inkscape:window-x="0"
35 inkscape:window-y="27"
36 inkscape:window-maximized="1" />
37 <metadata
38 id="metadata3057">
39 <rdf:RDF>
40 <cc:Work
41 rdf:about="">
42 <dc:format>image/svg+xml</dc:format>
43 <dc:type
44 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
45 <dc:title></dc:title>
46 </cc:Work>
47 </rdf:RDF>
48 </metadata>
49 <g
50 inkscape:label="Layer 1"
51 inkscape:groupmode="layer"
52 id="layer1"
53 transform="translate(0,-1020.3622)">
54 <rect
55 style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
56 id="rect3062"
57 width="30"
58 height="16.875"
59 x="1"
60 y="1027.9247" />
61 </g>
62 </svg>
+0
-70
voctogui/ui/composite-picture-in-picture.svg less more
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="32"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.48.5 r10040"
16 sodipodi:docname="composite-fullscreen.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="22.4"
27 inkscape:cx="5.5788399"
28 inkscape:cy="10.804879"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1920"
33 inkscape:window-height="1014"
34 inkscape:window-x="0"
35 inkscape:window-y="27"
36 inkscape:window-maximized="1" />
37 <metadata
38 id="metadata3057">
39 <rdf:RDF>
40 <cc:Work
41 rdf:about="">
42 <dc:format>image/svg+xml</dc:format>
43 <dc:type
44 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
45 <dc:title></dc:title>
46 </cc:Work>
47 </rdf:RDF>
48 </metadata>
49 <g
50 inkscape:label="Layer 1"
51 inkscape:groupmode="layer"
52 id="layer1"
53 transform="translate(0,-1020.3622)">
54 <rect
55 style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
56 id="rect3062"
57 width="30"
58 height="16.875"
59 x="1"
60 y="1027.9247" />
61 <rect
62 style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.14390306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
63 id="rect3062-6"
64 width="8.6341839"
65 height="4.8567281"
66 x="21.332664"
67 y="1038.9114" />
68 </g>
69 </svg>
+0
-81
voctogui/ui/composite-side-by-side-equal.svg less more
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="32"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.48.5 r10040"
16 sodipodi:docname="composite-side-by-side-preview.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="15.839192"
27 inkscape:cx="-1.5194006"
28 inkscape:cy="11.71477"
29 inkscape:document-units="px"
30 inkscape:current-layer="g3943"
31 showgrid="false"
32 inkscape:window-width="1920"
33 inkscape:window-height="1014"
34 inkscape:window-x="0"
35 inkscape:window-y="27"
36 inkscape:window-maximized="1" />
37 <metadata
38 id="metadata3057">
39 <rdf:RDF>
40 <cc:Work
41 rdf:about="">
42 <dc:format>image/svg+xml</dc:format>
43 <dc:type
44 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
45 <dc:title></dc:title>
46 </cc:Work>
47 </rdf:RDF>
48 </metadata>
49 <g
50 inkscape:label="Layer 1"
51 inkscape:groupmode="layer"
52 id="layer1"
53 transform="translate(0,-1020.3622)">
54 <rect
55 style="color:#000000;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.10000000000000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
56 id="rect3062"
57 width="30"
58 height="16.875"
59 x="1"
60 y="1027.9247" />
61 <g
62 id="g3943"
63 transform="matrix(0.9768158,0,0,0.9768158,0.70836066,24.02723)">
64 <rect
65 y="1032.0743"
66 x="0.37704754"
67 height="8.57582"
68 width="15.245902"
69 id="rect3062-3"
70 style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.25409836;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
71 <rect
72 y="1032.0743"
73 x="15.686207"
74 height="8.57582"
75 width="15.245902"
76 id="rect3062-3-8"
77 style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.25409836000000002;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
78 </g>
79 </g>
80 </svg>
+0
-77
voctogui/ui/composite-side-by-side-preview.svg less more
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="32"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.48.5 r10040"
16 sodipodi:docname="composite-picture-in-picture.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="22.4"
27 inkscape:cx="15.069683"
28 inkscape:cy="13.944982"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1920"
33 inkscape:window-height="1014"
34 inkscape:window-x="0"
35 inkscape:window-y="27"
36 inkscape:window-maximized="1" />
37 <metadata
38 id="metadata3057">
39 <rdf:RDF>
40 <cc:Work
41 rdf:about="">
42 <dc:format>image/svg+xml</dc:format>
43 <dc:type
44 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
45 <dc:title></dc:title>
46 </cc:Work>
47 </rdf:RDF>
48 </metadata>
49 <g
50 inkscape:label="Layer 1"
51 inkscape:groupmode="layer"
52 id="layer1"
53 transform="translate(0,-1020.3622)">
54 <rect
55 style="color:#000000;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.10000000000000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
56 id="rect3062"
57 width="30"
58 height="16.875"
59 x="1"
60 y="1027.9247" />
61 <rect
62 style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.14390306;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
63 id="rect3062-6"
64 width="8.6341839"
65 height="4.8567281"
66 x="22.311062"
67 y="1039.8778" />
68 <rect
69 style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.34266803;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
70 id="rect3062-3"
71 width="20.560081"
72 height="11.565046"
73 x="0.92133397"
74 y="1027.8459" />
75 </g>
76 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="32"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.92.4 (unknown)"
16 sodipodi:docname="headphones.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="16"
27 inkscape:cx="-5.106995"
28 inkscape:cy="18.094208"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1158"
34 inkscape:window-x="0"
35 inkscape:window-y="18"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1020.3622)">
58 <g
59 id="g1560"
60 transform="translate(-0.36304836,-0.41778462)"
61 style="fill:#ffffff;stroke:none">
62 <text
63 id="text4799"
64 y="1036.7174"
65 x="22.70282"
66 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.46192551px;line-height:125%;font-family:'Arial Black';-inkscape-font-specification:'Arial Black, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.46154815px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
67 xml:space="preserve"><tspan
68 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:18.46192551px;font-family:'Droid Sans Fallback';-inkscape-font-specification:'Droid Sans Fallback, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.46154815px;fill:#ffffff;stroke:none"
69 y="1053.27"
70 x="22.70282"
71 id="tspan4797"
72 sodipodi:role="line" /></text>
73 <path
74 inkscape:connector-curvature="0"
75 id="path982"
76 d="m 7.5330739,1045.5065 c -0.581564,-0.143 -1.10353,-0.5832 -1.39186,-1.174 -0.243226,-0.4984 -0.241079,-0.4676 -0.229841,-3.3006 l 0.01001,-2.5243 0.101405,-0.2907 c 0.20203,-0.579 0.591803,-1.025 1.119514,-1.2808 l 0.295058,-0.143 0.02274,-0.343 c 0.09734,-1.4677 0.534063,-2.8943 1.247386,-4.0747 0.656451,-1.0863 1.4349701,-1.9409 2.4601261,-2.7007 1.198018,-0.8879 2.7283,-1.4801 4.242503,-1.6418 0.428402,-0.046 1.477461,-0.046 1.905863,0 1.501867,0.1604 2.978887,0.7289 4.203864,1.6182 1.035257,0.7515 1.861802,1.6575 2.501982,2.7425 0.325141,0.5511 0.302061,0.5074 0.473815,0.8952 0.422015,0.953 0.683094,2.0322 0.768853,3.1785 l 0.02437,0.3258 0.294992,0.1436 c 0.529459,0.2578 0.917599,0.7017 1.11945,1.2802 l 0.101405,0.2907 0.01001,2.5243 c 0.01125,2.833 0.01339,2.8022 -0.22984,3.3006 -0.227632,0.4664 -0.581828,0.8264 -1.014658,1.0314 -0.367942,0.1742 -0.505097,0.1905 -1.594112,0.1905 h -0.990644 l -0.01433,-4.6207 c -0.01387,-4.4697 -0.01662,-4.6331 -0.0842,-4.9979 -0.286035,-1.5443 -0.872684,-2.6945 -1.892584,-3.7104 -1.000409,-0.9966 -2.236298,-1.629 -3.616491,-1.8505 -0.470312,-0.076 -1.558655,-0.076 -2.028252,-10e-5 -2.407499,0.3865 -4.396341,2.0557 -5.210662,4.3733 -0.1417321,0.4034 -0.2089821,0.6619 -0.3000251,1.1534 -0.0676,0.365 -0.07033,0.5275 -0.08398,5.0151 l -0.01411,4.6379 h -1.018153 c -0.679833,0 -1.075127,-0.017 -1.189609,-0.045 z m 3.4119721,0 c -0.157307,-0.071 -0.297309,-0.2064 -0.388917,-0.3762 l -0.07402,-0.1371 -0.0098,-3.6863 c -0.0069,-2.589 0.0013,-3.7323 0.02779,-3.8406 0.111641,-0.4581 0.615251,-0.7134 1.049482,-0.5319 0.181726,0.076 0.282166,0.1765 0.376966,0.3776 l 0.08085,0.1714 3.3e-4,3.7424 3.29e-4,3.7424 -0.0943,0.1792 c -0.13931,0.2648 -0.320581,0.3826 -0.614867,0.3996 -0.164174,0.01 -0.269597,0 -0.353858,-0.041 z m 10.235918,-0.012 c -0.184836,-0.085 -0.315501,-0.214 -0.395208,-0.3895 -0.06412,-0.1413 -0.06623,-0.2641 -0.06664,-3.8837 l -5.08e-4,-3.7377 0.0817,-0.1755 c 0.297216,-0.6383 1.231033,-0.5458 1.425308,0.1412 0.02807,0.099 0.03595,1.1609 0.02853,3.8406 l -0.01026,3.7035 -0.07402,0.1371 c -0.202913,0.3761 -0.625312,0.5315 -0.989011,0.364 z"
77 style="fill:#ffffff;stroke-width:0.03429119;stroke:none" />
78 </g>
79 </g>
80 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:dc="http://purl.org/dc/elements/1.1/"
3 xmlns:cc="http://creativecommons.org/ns#"
4 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
5 xmlns:svg="http://www.w3.org/2000/svg"
6 xmlns="http://www.w3.org/2000/svg"
7 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
8 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
9 width="80"
10 height="32"
11 viewBox="0 0 21.166668 8.4666667"
12 version="1.1"
13 id="svg8"
14 sodipodi:docname="logo.svg"
15 inkscape:version="0.92.3 (2405546, 2018-03-11)">
16 <sodipodi:namedview
17 pagecolor="#ffffff"
18 bordercolor="#666666"
19 borderopacity="1"
20 objecttolerance="10"
21 gridtolerance="10"
22 guidetolerance="10"
23 inkscape:pageopacity="0"
24 inkscape:pageshadow="2"
25 inkscape:window-width="1592"
26 inkscape:window-height="855"
27 id="namedview15079"
28 showgrid="false"
29 units="px"
30 inkscape:zoom="1.9196365"
31 inkscape:cx="-52.874593"
32 inkscape:cy="28.346457"
33 inkscape:window-x="0"
34 inkscape:window-y="20"
35 inkscape:window-maximized="0"
36 inkscape:current-layer="svg8" />
37 <defs
38 id="defs2" />
39 <metadata
40 id="metadata5">
41 <rdf:RDF>
42 <cc:Work
43 rdf:about="">
44 <dc:format>image/svg+xml</dc:format>
45 <dc:type
46 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
47 <dc:title></dc:title>
48 </cc:Work>
49 </rdf:RDF>
50 </metadata>
51 <g
52 id="layer1"
53 transform="matrix(0.42526937,0,0,0.42526937,0.75690248,-118.98632)">
54 <g
55 id="text817"
56 style="font-style:normal;font-weight:normal;font-size:14.73244286px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#a9a9a9;fill-opacity:1;stroke:none;stroke-width:0.36831108px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
57 aria-label="VOC2MIX">
58 <path
59 id="path823"
60 style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:condensed;font-size:14.73244286px;font-family:'basic title font';-inkscape-font-specification:'basic title font, Thin Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#a9a9a9;stroke-width:0.36831108px"
61 d="M 5.6335748,284.18894 H 5.9576886 L 3.8067519,294.78157 H 3.6741599 3.644695 3.6152301 3.4826382 L 1.3169691,284.18894 h 0.3241137 l 2.0036122,9.87074 z"
62 inkscape:connector-curvature="0" />
63 <path
64 id="path825"
65 style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:condensed;font-size:14.73244286px;font-family:'basic title font';-inkscape-font-specification:'basic title font, Thin Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#a9a9a9;stroke-width:0.36831108px"
66 d="m 12.951916,292.52751 q 0,0.0442 0,0.23571 0,0.19153 -0.05893,0.47144 -0.05893,0.26519 -0.191522,0.57457 -0.117859,0.30938 -0.368311,0.57456 -0.294648,0.29465 -0.839749,0.45671 -0.530368,0.14732 -1.031271,0.14732 -0.5009029,0 -1.0312709,-0.14732 -0.5303679,-0.16206 -0.8102843,-0.45671 -0.2504515,-0.26518 -0.3830435,-0.58929 -0.1178596,-0.32412 -0.1620569,-0.60403 -0.044197,-0.29465 -0.044197,-0.50091 0,-0.20625 0,-0.25045 v -6.0403 q 0,-0.5893 0.1620568,-1.046 0.1767894,-0.47144 0.5451004,-0.79556 0.1473245,-0.13259 0.3535787,-0.23571 0.2209866,-0.10313 0.4567057,-0.16206 0.2357191,-0.0737 0.471438,-0.10313 0.235719,-0.0442 0.441973,-0.0442 0.191522,0 0.427241,0.0295 0.250452,0.0295 0.486171,0.10313 0.250451,0.0589 0.456705,0.16205 0.220987,0.0884 0.368311,0.22099 0.751355,0.66296 0.751355,1.87102 z m -0.309381,-6.1287 q 0,-0.51564 -0.147325,-0.88395 -0.132592,-0.36831 -0.353578,-0.61876 -0.147325,-0.14732 -0.368311,-0.25045 -0.206255,-0.11786 -0.441974,-0.19152 -0.220986,-0.0737 -0.456705,-0.10313 -0.23572,-0.0295 -0.412509,-0.0295 -0.338846,0 -0.7218896,0.0884 -0.368311,0.0737 -0.6776923,0.30938 -0.3093813,0.22099 -0.5156355,0.6335 -0.1915218,0.39777 -0.1915218,1.046 v 6.05503 q 0,0 -0.014732,0.19153 0,0.17678 0.044197,0.44197 0.044197,0.26518 0.1473244,0.55983 0.1031271,0.29465 0.3241138,0.53037 0.2357191,0.25045 0.7071572,0.38304 0.4714378,0.11786 0.8986788,0.11786 0.220987,0 0.456706,-0.0295 0.235719,-0.0295 0.456706,-0.0884 0.235719,-0.0737 0.42724,-0.16205 0.191522,-0.10313 0.309382,-0.23572 0.220986,-0.23572 0.324113,-0.51564 0.103128,-0.27991 0.147325,-0.51563 0.0442,-0.25046 0.0442,-0.41251 0,-0.17679 0,-0.17679 z"
67 inkscape:connector-curvature="0" />
68 <path
69 id="path827"
70 style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:condensed;font-size:14.73244286px;font-family:'basic title font';-inkscape-font-specification:'basic title font, Thin Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#a9a9a9;stroke-width:0.36831108px"
71 d="M 19.553891,292.39491 H 19.24451 Z m 0,0 q 0,0.0147 0,0.23572 0,0.20626 -0.05893,0.51564 -0.0442,0.30938 -0.17679,0.64823 -0.117859,0.33884 -0.368311,0.60403 -0.574565,0.60403 -1.591103,0.60403 -1.016539,0 -1.546907,-0.60403 -0.265184,-0.27992 -0.397776,-0.60403 -0.117859,-0.32412 -0.162057,-0.60403 -0.0442,-0.29465 -0.0442,-0.50091 0.01473,-0.20625 0.01473,-0.25045 v -6.05503 q 0,-1.1786 0.707158,-1.84156 0.279916,-0.25045 0.648227,-0.39777 0.383044,-0.14733 0.78082,-0.14733 0.795551,0 1.443779,0.53037 0.368311,0.30938 0.559833,0.85448 0.206254,0.53037 0.206254,1.16386 H 19.24451 q 0,-0.57456 -0.176789,-1.0018 -0.162057,-0.42724 -0.441973,-0.69243 -0.265184,-0.27991 -0.604031,-0.41251 -0.338846,-0.13259 -0.662959,-0.13259 -0.338847,0 -0.677693,0.13259 -0.324114,0.11786 -0.589298,0.36832 -0.250451,0.25045 -0.412508,0.64822 -0.147324,0.39778 -0.147324,0.92815 v 6.06976 0.0147 q 0,0 0,0.19152 0,0.17679 0.02946,0.44197 0.0442,0.25045 0.147325,0.5451 0.117859,0.29465 0.338846,0.53037 0.471438,0.51563 1.311188,0.51563 0.869214,0 1.355384,-0.51563 0.220987,-0.23572 0.324114,-0.53037 0.11786,-0.30938 0.162057,-0.5893 0.0442,-0.27991 0.0442,-0.47143 0,-0.19153 0,-0.19153 z"
72 inkscape:connector-curvature="0" />
73 <path
74 id="path829"
75 style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:condensed;font-size:14.73244286px;font-family:'basic title font';-inkscape-font-specification:'basic title font, Thin Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#a9a9a9;stroke-width:0.36831108px"
76 d="m 22.078665,294.8847 v -0.14733 q 0,-0.66296 0.176789,-1.28172 0.191522,-0.6335 0.574566,-1.29646 0.338846,-0.57456 0.736622,-1.10493 0.412508,-0.5451 0.839749,-1.06073 0.486171,-0.5893 0.839749,-1.03128 0.368311,-0.4567 0.604031,-0.85448 0.250451,-0.39777 0.368311,-0.79555 0.132592,-0.39777 0.132592,-0.89868 v -0.0147 q 0,-0.13259 -0.01473,-0.26518 0,-0.1326 -0.02947,-0.26519 -0.0442,-0.23572 -0.147324,-0.5009 -0.103127,-0.26519 -0.309381,-0.48617 -0.486171,-0.51564 -1.370118,-0.51564 -0.913411,0 -1.325919,0.50091 -0.191522,0.22098 -0.279917,0.51563 -0.07366,0.29465 -0.103127,0.55983 -0.01473,0.25046 -0.01473,0.44198 0.01473,0.17679 0.01473,0.17679 h -0.309381 q 0,-0.0147 -0.01473,-0.22099 -0.01473,-0.20625 0.01473,-0.48617 0.02947,-0.27992 0.117859,-0.5893 0.103127,-0.32411 0.324114,-0.5893 0.515635,-0.61876 1.576371,-0.61876 1.031271,0 1.605837,0.61876 0.235719,0.25046 0.353578,0.5451 0.132592,0.29465 0.17679,0.5451 0.05893,0.25046 0.05893,0.44198 0,0.17679 0,0.22098 0,0.53037 -0.176789,1.01654 -0.162057,0.47144 -0.456706,0.92815 -0.279916,0.44197 -0.648227,0.89868 -0.368311,0.44197 -0.751355,0.91341 -0.412508,0.5009 -0.810284,1.01654 -0.383044,0.5009 -0.707157,1.046 -0.309382,0.53037 -0.515636,1.11967 -0.191522,0.57456 -0.220987,1.20806 l 4.03669,-0.0295 v 0.33885 z"
77 inkscape:connector-curvature="0" />
78 <path
79 id="path831"
80 style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:condensed;font-size:14.73244286px;font-family:'basic title font';-inkscape-font-specification:'basic title font, Thin Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#a9a9a9;stroke-width:0.36831108px"
81 d="m 29.354188,284.91083 v 9.92967 h -0.147325 -0.162056 v -10.56316 -0.0147 -0.1326 h 0.250451 l 2.725502,5.33315 2.725502,-5.33315 h 0.250451 v 0.1326 0.0147 10.56316 H 34.834657 34.6726 v -9.9444 l -2.548713,5.08269 h -0.206254 z m 5.436271,-0.78082 h 0.01473 0.01473 0.01473 0.01473 0.01473 0.01473 0.01473 z m -5.539398,0 h -0.01473 -0.01473 -0.01473 -0.01473 -0.01473 -0.01473 -0.01473 z"
82 inkscape:connector-curvature="0" />
83 <path
84 id="path833"
85 style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:condensed;font-size:14.73244286px;font-family:'basic title font';-inkscape-font-specification:'basic title font, Thin Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#a9a9a9;stroke-width:0.36831108px"
86 d="m 37.874147,294.8405 v -10.71049 h 0.162057 0.162057 v 10.71049 h -0.162057 z"
87 inkscape:connector-curvature="0" />
88 <path
89 id="path835"
90 style="font-style:normal;font-variant:normal;font-weight:100;font-stretch:condensed;font-size:14.73244286px;font-family:'basic title font';-inkscape-font-specification:'basic title font, Thin Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#a9a9a9;stroke-width:0.36831108px"
91 d="m 43.664686,290.4355 -0.279916,-0.73662 -0.279917,0.73662 -1.679498,4.405 h -0.338846 l 1.723696,-4.50813 0.397776,-1.046 -1.944683,-5.09743 h 0.324114 l 1.782625,4.81751 1.812091,-4.81751 h 0.324114 l -1.959415,5.09743 0.397776,1.046 1.738428,4.4934 h -0.338846 z"
92 inkscape:connector-curvature="0" />
93 </g>
94 </g>
95 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <svg
2 xmlns:dc="http://purl.org/dc/elements/1.1/"
3 xmlns:cc="http://creativecommons.org/ns#"
4 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
5 xmlns:svg="http://www.w3.org/2000/svg"
6 xmlns="http://www.w3.org/2000/svg"
7 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
8 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
9 version="1.1"
10 id="svg3052"
11 height="32"
12 width="32"
13 sodipodi:docname="mute.svg"
14 inkscape:version="0.92.4 (unknown)">
15 <sodipodi:namedview
16 pagecolor="#ffffff"
17 bordercolor="#666666"
18 borderopacity="1"
19 objecttolerance="10"
20 gridtolerance="10"
21 guidetolerance="10"
22 inkscape:pageopacity="0"
23 inkscape:pageshadow="2"
24 inkscape:window-width="1912"
25 inkscape:window-height="1158"
26 id="namedview1012"
27 showgrid="false"
28 inkscape:zoom="19.666667"
29 inkscape:cx="4.1825884"
30 inkscape:cy="13.092021"
31 inkscape:window-x="0"
32 inkscape:window-y="18"
33 inkscape:window-maximized="0"
34 inkscape:current-layer="svg3052" />
35 <defs
36 id="defs3054" />
37 <metadata
38 id="metadata3057">
39 <rdf:RDF>
40 <cc:Work
41 rdf:about="">
42 <dc:format>image/svg+xml</dc:format>
43 <dc:type
44 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
45 <dc:title></dc:title>
46 </cc:Work>
47 </rdf:RDF>
48 </metadata>
49 <g
50 transform="matrix(0.49928774,0,0,0.49928774,-0.31816293,-497.44864)"
51 id="layer1"
52 style="fill:#dcdcdc">
53 <text
54 id="text4799"
55 y="1027.1123"
56 x="46.892715"
57 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:125%;font-family:'Arial Black';-inkscape-font-specification:'Arial Black, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#dcdcdc;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
58 xml:space="preserve"><tspan
59 style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;font-family:'Droid Sans Fallback';-inkscape-font-specification:'Droid Sans Fallback, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#dcdcdc"
60 y="1062.9755"
61 x="46.892715"
62 id="tspan4797" /></text>
63 <path
64 id="path1880"
65 d="m 10.973219,1020.2872 -0.02209,16.1501 h 10.162857 l 10.162859,10.1628 0.02211,-36.4758 -10.162859,10.1629 z"
66 style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.01628578px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
67 inkscape:connector-curvature="0" />
68 <path
69 style="opacity:1;fill:#fbfbfb;fill-opacity:0.50196081;fill-rule:evenodd;stroke:none;stroke-width:0.18870717;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.17014182;stroke-opacity:1;paint-order:stroke fill markers"
70 d="M 18.90625 11.111328 L 16.701172 13.318359 L 19.570312 16.1875 L 16.701172 19.056641 L 18.90625 21.261719 L 21.775391 18.392578 L 24.644531 21.261719 L 26.849609 19.056641 L 23.980469 16.1875 L 26.849609 13.318359 L 24.644531 11.111328 L 21.775391 13.980469 L 18.90625 11.111328 z "
71 transform="matrix(2.0028531,0,0,2.0028531,0.63723361,996.31655)"
72 id="rect936" />
73 </g>
74 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="nostream.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-41.27408"
28 inkscape:cy="-1.5665675"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1172"
34 inkscape:window-x="0"
35 inkscape:window-y="0"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 id="g33465"
60 transform="matrix(1.9011976,0,0,1.9011976,-2.6322472,-894.37802)">
61 <g
62 transform="matrix(0.17779359,0,0,0.17779359,-81.987716,953.58922)"
63 id="g8261">
64 <g
65 style="fill:#000000"
66 id="g8240"
67 transform="translate(206.81577,216.25459)">
68 <rect
69 rx="19.285715"
70 ry="19.285713"
71 y="50.17815"
72 x="275.3829"
73 height="116.67266"
74 width="162.79439"
75 id="rect8242"
76 style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
77 </g>
78 <g
79 transform="matrix(0.78282213,0,0,1,423.47741,218.19296)"
80 id="g8210" />
81 <g
82 transform="matrix(0.45187084,0,0,0.45187084,457.78898,297.34173)"
83 id="g8222" />
84 </g>
85 <path
86 inkscape:connector-curvature="0"
87 id="path18391"
88 d="M 4.7100612,1002.3461 31.403129,1019.976"
89 style="fill:#ff0000;fill-rule:evenodd;stroke:#ff0000;stroke-width:3.42124653;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
90 <path
91 style="fill:#ff0000;fill-rule:evenodd;stroke:#ff0000;stroke-width:3.42124653;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
92 d="M 31.403129,1002.3461 4.7100612,1019.976"
93 id="path18393"
94 inkscape:connector-curvature="0" />
95 <g
96 style="fill:none"
97 id="g18395"
98 transform="matrix(0.17779359,0,0,0.17779359,-81.987716,953.58922)">
99 <g
100 transform="translate(206.81577,216.25459)"
101 id="g18397"
102 style="fill:none">
103 <rect
104 style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
105 id="rect18399"
106 width="162.79439"
107 height="116.67266"
108 x="275.3829"
109 y="50.17815"
110 ry="19.285713"
111 rx="19.285715" />
112 </g>
113 <g
114 style="fill:none"
115 id="g18401"
116 transform="matrix(0.78282213,0,0,1,423.47741,218.19296)" />
117 <g
118 style="fill:none"
119 id="g18403"
120 transform="matrix(0.45187084,0,0,0.45187084,457.78898,297.34173)" />
121 </g>
122 </g>
123 </g>
124 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="24"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.92.3 (2405546, 2018-03-11)"
16 sodipodi:docname="ports.svg">
17 <defs
18 id="defs3054">
19 <marker
20 inkscape:isstock="true"
21 style="overflow:visible"
22 id="marker11707"
23 refX="0"
24 refY="0"
25 orient="auto"
26 inkscape:stockid="Arrow2Mend">
27 <path
28 transform="scale(-0.6)"
29 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
30 style="fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:#808080;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
31 id="path11705"
32 inkscape:connector-curvature="0" />
33 </marker>
34 <marker
35 inkscape:isstock="true"
36 style="overflow:visible"
37 id="marker11637"
38 refX="0"
39 refY="0"
40 orient="auto"
41 inkscape:stockid="Arrow2Mend">
42 <path
43 transform="scale(-0.6)"
44 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
45 style="fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:#808080;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
46 id="path11635"
47 inkscape:connector-curvature="0" />
48 </marker>
49 <marker
50 inkscape:stockid="Arrow2Mend"
51 orient="auto"
52 refY="0"
53 refX="0"
54 id="marker11573"
55 style="overflow:visible"
56 inkscape:isstock="true"
57 inkscape:collect="always">
58 <path
59 id="path11571"
60 style="fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:#808080;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
61 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
62 transform="scale(-0.6)"
63 inkscape:connector-curvature="0" />
64 </marker>
65 <marker
66 inkscape:isstock="true"
67 style="overflow:visible"
68 id="marker11491"
69 refX="0"
70 refY="0"
71 orient="auto"
72 inkscape:stockid="Arrow2Mend">
73 <path
74 transform="scale(-0.6)"
75 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
76 style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
77 id="path11489"
78 inkscape:connector-curvature="0" />
79 </marker>
80 <marker
81 inkscape:isstock="true"
82 style="overflow:visible"
83 id="marker11103"
84 refX="0"
85 refY="0"
86 orient="auto"
87 inkscape:stockid="Arrow2Mend">
88 <path
89 transform="scale(-0.6)"
90 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
91 style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
92 id="path11101"
93 inkscape:connector-curvature="0" />
94 </marker>
95 <marker
96 inkscape:isstock="true"
97 style="overflow:visible"
98 id="marker10895"
99 refX="0"
100 refY="0"
101 orient="auto"
102 inkscape:stockid="Arrow2Mend">
103 <path
104 transform="scale(-0.6)"
105 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
106 style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
107 id="path10893"
108 inkscape:connector-curvature="0" />
109 </marker>
110 <marker
111 inkscape:stockid="Arrow2Mend"
112 orient="auto"
113 refY="0"
114 refX="0"
115 id="Arrow2Mend"
116 style="overflow:visible"
117 inkscape:isstock="true"
118 inkscape:collect="always">
119 <path
120 id="path9420"
121 style="fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:#808080;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
122 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
123 transform="scale(-0.6)"
124 inkscape:connector-curvature="0" />
125 </marker>
126 <marker
127 inkscape:stockid="Arrow1Send"
128 orient="auto"
129 refY="0"
130 refX="0"
131 id="Arrow1Send"
132 style="overflow:visible"
133 inkscape:isstock="true">
134 <path
135 id="path9408"
136 d="M 0,0 5,-5 -12.5,0 5,5 Z"
137 style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
138 transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
139 inkscape:connector-curvature="0" />
140 </marker>
141 <marker
142 inkscape:stockid="Arrow1Mend"
143 orient="auto"
144 refY="0"
145 refX="0"
146 id="Arrow1Mend"
147 style="overflow:visible"
148 inkscape:isstock="true">
149 <path
150 id="path9402"
151 d="M 0,0 5,-5 -12.5,0 5,5 Z"
152 style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
153 transform="matrix(-0.4,0,0,-0.4,-4,0)"
154 inkscape:connector-curvature="0" />
155 </marker>
156 <marker
157 inkscape:stockid="Arrow1Lend"
158 orient="auto"
159 refY="0"
160 refX="0"
161 id="Arrow1Lend"
162 style="overflow:visible"
163 inkscape:isstock="true">
164 <path
165 id="path9396"
166 d="M 0,0 5,-5 -12.5,0 5,5 Z"
167 style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
168 transform="matrix(-0.8,0,0,-0.8,-10,0)"
169 inkscape:connector-curvature="0" />
170 </marker>
171 <marker
172 inkscape:stockid="Arrow2Mend"
173 orient="auto"
174 refY="0"
175 refX="0"
176 id="Arrow2Mend-7"
177 style="overflow:visible"
178 inkscape:isstock="true"
179 inkscape:collect="always">
180 <path
181 inkscape:connector-curvature="0"
182 id="path9420-6"
183 style="fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:#808080;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
184 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
185 transform="scale(-0.6)" />
186 </marker>
187 <marker
188 inkscape:stockid="Arrow2Mend"
189 orient="auto"
190 refY="0"
191 refX="0"
192 id="Arrow2Mend-7-9"
193 style="overflow:visible"
194 inkscape:isstock="true"
195 inkscape:collect="always">
196 <path
197 inkscape:connector-curvature="0"
198 id="path9420-6-7"
199 style="fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:#808080;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
200 d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
201 transform="scale(-0.6)" />
202 </marker>
203 </defs>
204 <sodipodi:namedview
205 id="base"
206 pagecolor="#ffffff"
207 bordercolor="#666666"
208 borderopacity="1.0"
209 inkscape:pageopacity="0.0"
210 inkscape:pageshadow="2"
211 inkscape:zoom="7.2407734"
212 inkscape:cx="10.896936"
213 inkscape:cy="40.423499"
214 inkscape:document-units="px"
215 inkscape:current-layer="layer1"
216 showgrid="false"
217 inkscape:window-width="1592"
218 inkscape:window-height="855"
219 inkscape:window-x="0"
220 inkscape:window-y="20"
221 inkscape:window-maximized="0"
222 showguides="true"
223 inkscape:guide-bbox="true"
224 inkscape:snap-to-guides="true"
225 inkscape:snap-global="true" />
226 <metadata
227 id="metadata3057">
228 <rdf:RDF>
229 <cc:Work
230 rdf:about="">
231 <dc:format>image/svg+xml</dc:format>
232 <dc:type
233 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
234 <dc:title></dc:title>
235 </cc:Work>
236 </rdf:RDF>
237 </metadata>
238 <g
239 inkscape:label="Layer 1"
240 inkscape:groupmode="layer"
241 id="layer1"
242 transform="translate(0,-1028.3622)">
243 <g
244 id="g14705"
245 transform="matrix(0.43913557,0,0,0.43913557,1.5352132,588.69427)">
246 <rect
247 ry="19.542114"
248 rx="4.8344059"
249 y="1008.9966"
250 x="18.049963"
251 height="39.084229"
252 width="28.385048"
253 id="rect9387"
254 style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#808080;stroke-width:3.02267027;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3.20000005;stroke-dasharray:none;stroke-dashoffset:1.17014182;stroke-opacity:1" />
255 <path
256 inkscape:connector-curvature="0"
257 id="path9391"
258 d="M 46.680096,1017.4212 H 58.72325"
259 style="fill:none;fill-rule:evenodd;stroke:#808080;stroke-width:1.5738219;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" />
260 <path
261 inkscape:connector-curvature="0"
262 id="path9391-0"
263 d="M 46.680096,1029.0297 H 58.72325"
264 style="fill:none;fill-rule:evenodd;stroke:#808080;stroke-width:1.5738219;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend-7)" />
265 <path
266 inkscape:connector-curvature="0"
267 id="path9391-0-5"
268 d="M 46.680096,1040.6383 H 58.72325"
269 style="fill:none;fill-rule:evenodd;stroke:#808080;stroke-width:1.5738219;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend-7-9)" />
270 <path
271 style="fill:none;fill-rule:evenodd;stroke:#808080;stroke-width:1.57228339;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker11637)"
272 d="m 13.29545,1017.4212 h 4.686828"
273 id="path10887"
274 inkscape:connector-curvature="0" />
275 <path
276 style="fill:none;fill-rule:evenodd;stroke:#808080;stroke-width:1.57228339;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker11573)"
277 d="m 13.29545,1029.0297 h 4.686828"
278 id="path10889"
279 inkscape:connector-curvature="0" />
280 <path
281 style="fill:none;fill-rule:evenodd;stroke:#808080;stroke-width:1.57228339;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker11707)"
282 d="m 13.29545,1040.6383 h 4.686828"
283 id="path10891"
284 inkscape:connector-curvature="0" />
285 </g>
286 </g>
287 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="24"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.92.3 (2405546, 2018-03-11)"
16 sodipodi:docname="queues.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="7.841208"
27 inkscape:cx="2.0340265"
28 inkscape:cy="15.44345"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1592"
33 inkscape:window-height="855"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1028.3622)">
58 <path
59 style="fill:#808080;stroke-width:0.0287325"
60 d="m 13.68623,1050.5854 c -1.766569,-0.04 -3.49135,-0.1643 -4.8900246,-0.3519 -2.293324,-0.3076 -3.7423007,-0.7795 -3.8777836,-1.263 -0.1042339,-0.3719 0.570361,-0.5512 2.5112846,-0.6677 0.160726,-0.01 0.291803,-0.021 0.29128,-0.025 -4.91e-4,0 -0.08305,-0.074 -0.183392,-0.1548 -0.405904,-0.3272 -0.882065,-0.82 -1.2050983,-1.2473 -0.7075302,-0.9359 -1.1516089,-1.9503 -1.3736737,-3.1377 l -0.045309,-0.2423 -0.00478,-0.9429 c -0.00457,-0.9019 -0.0035,-0.9516 0.025014,-1.1408 0.2753209,-1.8278 1.2277447,-3.4741 2.681934,-4.6357 0.404448,-0.3231 0.839391,-0.5978 1.324461,-0.8367 0.857158,-0.4222 1.7257396,-0.6593 2.6839156,-0.7327 0.289971,-0.022 1.047696,-0.01 1.32255,0.021 0.7116,0.081 1.419274,0.2649 2.056412,0.535 1.628676,0.6905 2.959561,1.9562 3.732929,3.5499 0.453257,0.9342 0.696009,1.9243 0.726014,2.9615 0.0048,0.1646 0.0021,0.3623 -0.0061,0.4536 -0.008,0.089 -0.01261,0.1637 -0.01022,0.1661 0.0066,0.01 0.112461,-0.2972 0.190906,-0.5478 0.386191,-1.2344 0.600671,-2.5553 0.627464,-3.8643 0.0092,-0.4507 0.02598,-0.655 0.07153,-0.8718 0.120251,-0.5726 0.393227,-1.0736 0.814349,-1.4946 0.460438,-0.4604 1.029603,-0.7462 1.683477,-0.8455 0.216791,-0.033 0.637807,-0.033 0.851562,0 0.651467,0.1007 1.213373,0.3838 1.677846,0.8453 0.89789,0.8922 1.131665,2.2353 0.590832,3.3945 -0.25958,0.5563 -0.752586,1.0689 -1.29566,1.347 l -0.116483,0.06 -0.0098,0.4586 c -0.02357,1.105 -0.103915,1.9435 -0.263173,2.747 -0.298803,1.5077 -0.851458,2.7277 -1.687412,3.7251 -0.155608,0.1856 -0.547212,0.5805 -0.73356,0.7398 -1.328948,1.1352 -3.109313,1.785 -5.379835,1.9636 -0.635643,0.05 -1.589295,0.062 -2.78146,0.035 z m -2.31671,-2.7318 c 0.475002,-0.1238 0.702769,-0.2225 1.058749,-0.4583 0.632367,-0.4189 1.155743,-1.0277 1.4967,-1.7411 0.290701,-0.6082 0.397401,-1.1222 0.361337,-1.7407 -0.04351,-0.7461 -0.197068,-1.2791 -0.499826,-1.7348 -0.132283,-0.199 -0.399561,-0.4671 -0.594214,-0.5958 -0.408412,-0.2702 -0.903266,-0.4001 -1.37673,-0.3615 -0.531692,0.043 -0.811185,0.2372 -0.905489,0.6278 -0.03694,0.153 -0.03437,0.4233 0.0052,0.5495 0.06025,0.1921 0.192807,0.3432 0.376795,0.4295 0.123456,0.058 0.230804,0.081 0.44398,0.096 0.214251,0.015 0.264022,0.027 0.394678,0.092 0.305803,0.154 0.47758,0.4537 0.459892,0.8024 -0.01818,0.3586 -0.236822,0.6465 -0.577137,0.7601 -0.140184,0.047 -0.284234,0.058 -0.49608,0.037 -0.339024,-0.033 -0.622789,-0.112 -0.915417,-0.2547 -0.7631646,-0.3721 -1.2718946,-1.0485 -1.4035246,-1.866 -0.02683,-0.1666 -0.02228,-0.5741 0.0083,-0.7464 0.184999,-1.0418 1.0023506,-1.8939 2.0646686,-2.1525 0.46951,-0.1144 0.984571,-0.1146 1.525233,0 0.754011,0.1586 1.519357,0.6005 2.034426,1.1745 0.525128,0.5852 0.873262,1.3104 1.05131,2.1899 0.114022,0.5632 0.147564,1.2609 0.08492,1.7663 -0.09623,0.7764 -0.392294,1.5617 -0.858149,2.2762 -0.05911,0.091 -0.105093,0.1672 -0.102195,0.1701 0.0029,0 0.098,-0.058 0.21133,-0.1343 1.344307,-0.9109 2.193272,-2.2888 2.399835,-3.8952 0.03161,-0.2458 0.03608,-1.0124 0.0073,-1.2463 -0.07996,-0.649 -0.239058,-1.2101 -0.499839,-1.763 -0.445768,-0.945 -1.211728,-1.7938 -2.111823,-2.34 -0.220921,-0.1341 -0.684457,-0.3595 -0.916231,-0.4456 -1.411253,-0.5239 -2.971259,-0.4551 -4.3171216,0.1905 -0.776647,0.3725 -1.478013,0.9443 -1.994497,1.6259 -0.614187,0.8105 -0.984084,1.7484 -1.094282,2.7744 -0.02433,0.2266 -0.02424,0.9112 1.57e-4,1.1196 0.143832,1.2293 0.601399,2.2527 1.410559,3.1549 0.651969,0.7269 1.587579,1.3097 2.5247696,1.5726 0.145547,0.041 0.477159,0.121 0.510027,0.1231 0.0077,0 0.112315,-0.024 0.232375,-0.056 z m 13.428462,-13.1308 c -0.138641,-0.071 -0.391751,-0.1604 -0.626707,-0.2207 -0.06584,-0.017 -0.12456,-0.035 -0.130491,-0.04 -0.0114,-0.01 0.05543,-0.959 0.102717,-1.4583 0.107144,-1.1314 0.277467,-1.8285 0.555885,-2.2753 0.172239,-0.2764 0.419625,-0.4677 0.71896,-0.5559 0.156832,-0.046 0.426255,-0.062 0.593871,-0.035 0.407343,0.066 0.788799,0.3616 0.969238,0.7522 0.146376,0.3168 0.149229,0.6891 0.0077,1.0012 -0.150296,0.3315 -0.443058,0.5783 -0.803977,0.6779 -0.07002,0.019 -0.165903,0.027 -0.408382,0.033 l -0.316876,0.01 -0.07853,0.1571 c -0.251922,0.5039 -0.371819,1.0071 -0.409679,1.7195 -0.0091,0.1723 -0.02029,0.3125 -0.02477,0.3115 -0.0045,0 -0.07149,-0.035 -0.148945,-0.074 z m -2.780822,-0.2666 c -0.0216,-0.047 -0.10001,-0.2176 -0.174246,-0.3776 -0.388925,-0.8383 -0.574139,-1.4014 -0.673337,-2.0469 -0.04044,-0.2632 -0.05165,-0.7236 -0.02118,-0.8704 0.09725,-0.4686 0.466849,-0.8638 0.93444,-0.999 0.09237,-0.026 0.143146,-0.032 0.32098,-0.031 0.250103,0 0.326465,0.018 0.535167,0.1192 0.429134,0.2094 0.714252,0.6283 0.735766,1.0808 0.01707,0.3591 -0.09563,0.6711 -0.340717,0.9436 -0.135511,0.1506 -0.393959,0.3166 -0.587616,0.3774 l -0.0799,0.025 -0.01009,0.1211 c -0.02557,0.3066 0.02216,0.6438 0.183723,1.2974 0.03499,0.1416 0.06362,0.265 0.06362,0.2742 0,0.011 -0.04524,0.02 -0.137311,0.025 -0.145671,0.01 -0.398567,0.058 -0.592672,0.1141 l -0.11736,0.034 z"
61 id="path9366"
62 inkscape:connector-curvature="0" />
63 </g>
64 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="side-by-side.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-18.368791"
28 inkscape:cy="-8.7808317"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1156"
34 inkscape:window-x="1920"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 id="g26166"
60 transform="translate(71.858806,41.649805)">
61 <g
62 style="fill:#000000"
63 id="g8232"
64 transform="matrix(0.33818321,0,0,0.33818321,-160.47192,950.01462)">
65 <rect
66 rx="19.285715"
67 ry="19.285713"
68 y="50.17815"
69 x="275.3829"
70 height="116.67266"
71 width="162.79439"
72 id="rect8234"
73 style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
74 </g>
75 <g
76 transform="matrix(0.16891997,0,0,0.16891997,-51.998466,968.66124)"
77 id="g8184">
78 <rect
79 rx="19.285715"
80 ry="19.285713"
81 y="48.52586"
82 x="75.449371"
83 height="116.67266"
84 width="162.79439"
85 id="rect8186"
86 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
87 <path
88 inkscape:connector-curvature="0"
89 id="path8188"
90 d="m 91.037227,71.671452 107.225493,0"
91 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
92 <path
93 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
94 d="m 91.037227,88.833834 63.768463,0"
95 id="path8190"
96 inkscape:connector-curvature="0" />
97 <path
98 inkscape:connector-curvature="0"
99 id="path8192"
100 d="m 91.037227,105.99619 63.768463,0"
101 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
102 <path
103 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
104 d="m 91.037227,123.15858 63.768463,0"
105 id="path8194"
106 inkscape:connector-curvature="0" />
107 <rect
108 ry="0"
109 rx="0"
110 y="93.87233"
111 x="165.66994"
112 height="49.597694"
113 width="12.753692"
114 id="rect8196"
115 style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
116 <rect
117 style="opacity:1;fill:#228b22;fill-opacity:1;fill-rule:evenodd;stroke:#228b22;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
118 id="rect8198"
119 width="12.753692"
120 height="31.175694"
121 x="188.34315"
122 y="112.29432"
123 rx="0"
124 ry="0" />
125 <rect
126 ry="0"
127 rx="0"
128 y="101.43008"
129 x="211.0164"
130 height="42.039951"
131 width="12.753692"
132 id="rect8200"
133 style="opacity:1;fill:#4169e1;fill-opacity:1;fill-rule:evenodd;stroke:#4169e1;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
134 </g>
135 <g
136 transform="matrix(0.16891997,0,0,0.16891997,-114.25311,968.38213)"
137 id="g8202">
138 <rect
139 style="opacity:1;fill:#dcdcdc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
140 id="rect8204"
141 width="162.79439"
142 height="116.67266"
143 x="273.87848"
144 y="50.17815"
145 ry="19.285713"
146 rx="19.285715" />
147 <path
148 inkscape:connector-curvature="0"
149 id="path8206"
150 d="m 405.90317,166.85081 0,-11.80915 c 0,-1.76639 -0.17797,-3.48877 -0.51665,-5.15223 -0.33866,-1.66346 -0.8378,-3.26758 -1.48147,-4.79444 -0.64366,-1.52685 -1.43187,-2.97676 -2.34685,-4.33334 -0.91498,-1.35657 -1.95757,-2.62016 -3.11018,-3.77278 -1.15262,-1.15262 -2.41492,-2.19392 -3.7715,-3.1089 -1.35657,-0.91497 -2.80777,-1.70318 -4.33463,-2.34685 -1.52686,-0.64366 -3.12969,-1.1428 -4.79315,-1.48147 -1.66346,-0.33867 -3.38712,-0.51664 -5.15351,-0.51664 l -47.23535,0 c -1.76638,0 -3.49005,0.17797 -5.1535,0.51664 -1.66346,0.33867 -3.26759,0.83781 -4.79445,1.48147 -1.52686,0.64367 -2.97676,1.43188 -4.33334,2.34685 -1.35658,0.91498 -2.61888,1.95628 -3.77149,3.1089 -1.15262,1.15262 -2.19521,2.41621 -3.11019,3.77278 -0.91498,1.35658 -1.70318,2.80649 -2.34685,4.33334 -0.64366,1.52686 -1.1428,3.13098 -1.48147,4.79444 -0.33867,1.66346 -0.51664,3.38584 -0.51664,5.15223 l 0,11.80915 98.25122,0 z"
151 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
152 <circle
153 r="22.437052"
154 cy="99.067307"
155 cx="356.06894"
156 id="circle8208"
157 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
158 </g>
159 </g>
160 </g>
161 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="side-by-side-preview-left.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="15.682415"
27 inkscape:cx="37.375424"
28 inkscape:cy="27.360113"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1592"
33 inkscape:window-height="872"
34 inkscape:window-x="0"
35 inkscape:window-y="0"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 transform="matrix(0.33796427,0,0,0.33796427,-322.89183,848.28672)"
60 id="g18893">
61 <g
62 id="g18655">
63 <g
64 style="fill:#000000"
65 id="g8240"
66 transform="translate(693.30673,424.30954)">
67 <rect
68 rx="19.285715"
69 ry="19.285713"
70 y="50.17815"
71 x="275.3829"
72 height="116.67266"
73 width="162.79439"
74 id="rect8242"
75 style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
76 </g>
77 <g
78 id="g8122-1"
79 transform="matrix(0.71934556,0,0,0.71934556,918.07454,445.72211)">
80 <rect
81 rx="19.285715"
82 ry="19.285713"
83 y="48.52586"
84 x="131.7243"
85 height="116.67266"
86 width="162.79439"
87 id="rect8068-7"
88 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
89 <path
90 inkscape:connector-curvature="0"
91 id="path8070-7"
92 d="m 147.31214,71.671452 107.22548,0"
93 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
94 <path
95 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
96 d="m 147.31214,88.833834 63.76845,0"
97 id="path8072-9"
98 inkscape:connector-curvature="0" />
99 <path
100 inkscape:connector-curvature="0"
101 id="path8074-7"
102 d="m 147.31214,105.99619 63.76845,0"
103 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
104 <path
105 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
106 d="m 147.31214,123.15858 63.76845,0"
107 id="path8076-9"
108 inkscape:connector-curvature="0" />
109 <rect
110 ry="0"
111 rx="0"
112 y="93.87233"
113 x="221.94485"
114 height="49.597694"
115 width="12.753692"
116 id="rect8078-8"
117 style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
118 <rect
119 style="opacity:1;fill:#228b22;fill-opacity:1;fill-rule:evenodd;stroke:#228b22;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
120 id="rect8080-8"
121 width="12.753692"
122 height="31.175694"
123 x="244.61809"
124 y="112.29432"
125 rx="0"
126 ry="0" />
127 <rect
128 ry="0"
129 rx="0"
130 y="101.43008"
131 x="267.29135"
132 height="42.039951"
133 width="12.753692"
134 id="rect8082-9"
135 style="opacity:1;fill:#4169e1;fill-opacity:1;fill-rule:evenodd;stroke:#4169e1;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
136 </g>
137 <g
138 transform="matrix(0.42149352,0,0,0.42149352,953.15607,506.82051)"
139 id="g8222">
140 <rect
141 style="opacity:1;fill:#dcdcdc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
142 id="rect8224"
143 width="122.55557"
144 height="131.20334"
145 x="47.846043"
146 y="50.17815"
147 ry="19.285713"
148 rx="19.285715" />
149 <path
150 inkscape:connector-curvature="0"
151 id="path8226"
152 d="m 152.29166,181.69435 0,-14.02664 c 0,-2.09809 -0.15095,-4.1439 -0.4382,-6.11971 -0.28724,-1.97582 -0.71058,-3.88116 -1.25652,-5.69472 -0.54591,-1.81357 -1.21444,-3.53575 -1.99048,-5.14706 -0.77604,-1.6113 -1.66031,-3.11218 -2.6379,-4.48123 -0.97759,-1.36904 -2.04821,-2.60588 -3.19881,-3.69267 -1.15057,-1.08679 -2.3814,-2.02301 -3.67642,-2.78755 -1.295,-0.76452 -2.65445,-1.35739 -4.06531,-1.75966 -1.41087,-0.40226 -2.8728,-0.61364 -4.37095,-0.61364 l -40.062751,0 c -1.49814,0 -2.96008,0.21138 -4.37094,0.61364 -1.41086,0.40227 -2.77141,0.99514 -4.06642,1.75966 -1.29501,0.76454 -2.52474,1.70076 -3.67532,2.78755 -1.1506,1.08679 -2.221209,2.32363 -3.198799,3.69267 -0.97759,1.36905 -1.86187,2.86993 -2.63791,4.48123 -0.77604,1.61131 -1.44456,3.33349 -1.99048,5.14706 -0.54593,1.81356 -0.96927,3.7189 -1.25652,5.69472 -0.28724,1.97581 -0.43819,4.02162 -0.43819,6.11971 l 0,14.02664 83.33192,0 z"
153 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.63750124;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
154 <circle
155 r="22.437052"
156 cy="106.89152"
157 cx="109.91709"
158 id="circle8228"
159 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
160 </g>
161 </g>
162 </g>
163 </g>
164 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="side-by-side-preview.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-41.27408"
28 inkscape:cy="-8.7808317"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1156"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 transform="matrix(0.33796427,0,0,0.33796427,-322.89183,848.28672)"
60 id="g18893">
61 <g
62 id="g18655">
63 <g
64 style="fill:#000000"
65 id="g8240"
66 transform="translate(693.30673,424.30954)">
67 <rect
68 rx="19.285715"
69 ry="19.285713"
70 y="50.17815"
71 x="275.3829"
72 height="116.67266"
73 width="162.79439"
74 id="rect8242"
75 style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
76 </g>
77 <g
78 id="g8122-1"
79 transform="matrix(0.71934556,0,0,0.71934556,918.07454,445.72211)">
80 <rect
81 rx="19.285715"
82 ry="19.285713"
83 y="48.52586"
84 x="75.449371"
85 height="116.67266"
86 width="162.79439"
87 id="rect8068-7"
88 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
89 <path
90 inkscape:connector-curvature="0"
91 id="path8070-7"
92 d="m 91.037227,71.671452 107.225493,0"
93 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
94 <path
95 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
96 d="m 91.037227,88.833834 63.768463,0"
97 id="path8072-9"
98 inkscape:connector-curvature="0" />
99 <path
100 inkscape:connector-curvature="0"
101 id="path8074-7"
102 d="m 91.037227,105.99619 63.768463,0"
103 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
104 <path
105 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
106 d="m 91.037227,123.15858 63.768463,0"
107 id="path8076-9"
108 inkscape:connector-curvature="0" />
109 <rect
110 ry="0"
111 rx="0"
112 y="93.87233"
113 x="165.66994"
114 height="49.597694"
115 width="12.753692"
116 id="rect8078-8"
117 style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
118 <rect
119 style="opacity:1;fill:#228b22;fill-opacity:1;fill-rule:evenodd;stroke:#228b22;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
120 id="rect8080-8"
121 width="12.753692"
122 height="31.175694"
123 x="188.34315"
124 y="112.29432"
125 rx="0"
126 ry="0" />
127 <rect
128 ry="0"
129 rx="0"
130 y="101.43008"
131 x="211.0164"
132 height="42.039951"
133 width="12.753692"
134 id="rect8082-9"
135 style="opacity:1;fill:#4169e1;fill-opacity:1;fill-rule:evenodd;stroke:#4169e1;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
136 </g>
137 <g
138 transform="matrix(0.42149352,0,0,0.42149352,953.15607,506.82051)"
139 id="g8222">
140 <rect
141 style="opacity:1;fill:#dcdcdc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
142 id="rect8224"
143 width="122.55557"
144 height="131.20334"
145 x="295.50232"
146 y="50.17815"
147 ry="19.285713"
148 rx="19.285715" />
149 <path
150 inkscape:connector-curvature="0"
151 id="path8226"
152 d="m 399.94795,181.69435 0,-14.02664 c 0,-2.09809 -0.15095,-4.1439 -0.4382,-6.11971 -0.28724,-1.97582 -0.71058,-3.88116 -1.25652,-5.69472 -0.54591,-1.81357 -1.21444,-3.53575 -1.99048,-5.14706 -0.77604,-1.6113 -1.66031,-3.11218 -2.6379,-4.48123 -0.97759,-1.36904 -2.04821,-2.60588 -3.19881,-3.69267 -1.15057,-1.08679 -2.3814,-2.02301 -3.67642,-2.78755 -1.295,-0.76452 -2.65445,-1.35739 -4.06531,-1.75966 -1.41087,-0.40226 -2.8728,-0.61364 -4.37095,-0.61364 l -40.06275,0 c -1.49814,0 -2.96008,0.21138 -4.37094,0.61364 -1.41086,0.40227 -2.77141,0.99514 -4.06642,1.75966 -1.29501,0.76454 -2.52474,1.70076 -3.67532,2.78755 -1.1506,1.08679 -2.22121,2.32363 -3.1988,3.69267 -0.97759,1.36905 -1.86187,2.86993 -2.63791,4.48123 -0.77604,1.61131 -1.44456,3.33349 -1.99048,5.14706 -0.54593,1.81356 -0.96927,3.7189 -1.25652,5.69472 -0.28724,1.97581 -0.43819,4.02162 -0.43819,6.11971 l 0,14.02664 83.33192,0 z"
153 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.63750124;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
154 <circle
155 r="22.437052"
156 cy="106.89152"
157 cx="357.57336"
158 id="circle8228"
159 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
160 </g>
161 </g>
162 </g>
163 </g>
164 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="side-by-side-left.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="16.213695"
27 inkscape:cx="32"
28 inkscape:cy="21.532949"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1592"
33 inkscape:window-height="856"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 id="g26166"
60 transform="translate(71.858806,41.649805)">
61 <g
62 style="fill:#000000"
63 id="g8232"
64 transform="matrix(0.33818321,0,0,0.33818321,-160.47192,950.01462)">
65 <rect
66 rx="19.285715"
67 ry="19.285713"
68 y="50.17815"
69 x="275.3829"
70 height="116.67266"
71 width="162.79439"
72 id="rect8234"
73 style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
74 </g>
75 <g
76 transform="matrix(0.16891997,0,0,0.16891997,-51.998466,968.66124)"
77 id="g8184">
78 <rect
79 rx="19.285715"
80 ry="19.285713"
81 y="48.52586"
82 x="-93.60173"
83 height="116.67266"
84 width="162.79439"
85 id="rect8186"
86 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
87 <path
88 inkscape:connector-curvature="0"
89 id="path8188"
90 d="m -78.013872,71.671452 107.225493,0"
91 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
92 <path
93 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
94 d="m -78.013872,88.833834 63.768462,0"
95 id="path8190"
96 inkscape:connector-curvature="0" />
97 <path
98 inkscape:connector-curvature="0"
99 id="path8192"
100 d="m -78.013872,105.99619 63.768462,0"
101 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
102 <path
103 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
104 d="m -78.013872,123.15858 63.768462,0"
105 id="path8194"
106 inkscape:connector-curvature="0" />
107 <rect
108 ry="0"
109 rx="0"
110 y="93.87233"
111 x="-3.3811617"
112 height="49.597694"
113 width="12.753692"
114 id="rect8196"
115 style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
116 <rect
117 style="opacity:1;fill:#228b22;fill-opacity:1;fill-rule:evenodd;stroke:#228b22;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
118 id="rect8198"
119 width="12.753692"
120 height="31.175694"
121 x="19.292055"
122 y="112.29432"
123 rx="0"
124 ry="0" />
125 <rect
126 ry="0"
127 rx="0"
128 y="101.43008"
129 x="41.965305"
130 height="42.039951"
131 width="12.753692"
132 id="rect8200"
133 style="opacity:1;fill:#4169e1;fill-opacity:1;fill-rule:evenodd;stroke:#4169e1;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
134 </g>
135 <g
136 transform="matrix(0.16891997,0,0,0.16891997,-114.25311,968.38213)"
137 id="g8202">
138 <rect
139 style="opacity:1;fill:#dcdcdc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
140 id="rect8204"
141 width="162.79439"
142 height="116.67266"
143 x="444.02493"
144 y="50.178146"
145 ry="19.285713"
146 rx="19.285715" />
147 <path
148 inkscape:connector-curvature="0"
149 id="path8206"
150 d="m 576.04963,166.85081 0,-11.80915 c 0,-1.76639 -0.17797,-3.48877 -0.51665,-5.15223 -0.33866,-1.66346 -0.8378,-3.26758 -1.48147,-4.79444 -0.64366,-1.52685 -1.43187,-2.97676 -2.34685,-4.33334 -0.91498,-1.35657 -1.95757,-2.62016 -3.11018,-3.77278 -1.15262,-1.15262 -2.41492,-2.19392 -3.7715,-3.1089 -1.35657,-0.91497 -2.80777,-1.70318 -4.33463,-2.34685 -1.52686,-0.64366 -3.12969,-1.1428 -4.79315,-1.48147 -1.66346,-0.33867 -3.38712,-0.51664 -5.15351,-0.51664 l -47.23535,0 c -1.76638,0 -3.49005,0.17797 -5.1535,0.51664 -1.66346,0.33867 -3.26759,0.83781 -4.79445,1.48147 -1.52686,0.64367 -2.97676,1.43188 -4.33334,2.34685 -1.35658,0.91498 -2.61888,1.95628 -3.77149,3.1089 -1.15262,1.15262 -2.19521,2.41621 -3.11019,3.77278 -0.91498,1.35658 -1.70318,2.80649 -2.34685,4.33334 -0.64366,1.52686 -1.1428,3.13098 -1.48147,4.79444 -0.33867,1.66346 -0.51664,3.38584 -0.51664,5.15223 l 0,11.80915 98.25122,0 z"
151 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
152 <circle
153 r="22.437052"
154 cy="99.067307"
155 cx="526.21539"
156 id="circle8208"
157 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
158 </g>
159 </g>
160 </g>
161 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="slides-in-speaker.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-41.27408"
28 inkscape:cy="-8.7808317"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1156"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 transform="matrix(0.33773362,0,0,0.33773362,-194.55526,848.45843)"
60 id="g18859">
61 <g
62 transform="translate(502.92893,214.05327)"
63 id="g8275">
64 <g
65 id="g8158"
66 transform="translate(-188.89852,210.11173)">
67 <rect
68 rx="19.285715"
69 ry="19.285713"
70 y="50.17815"
71 x="275.3829"
72 height="116.67266"
73 width="162.79439"
74 id="rect8160"
75 style="opacity:1;fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
76 <path
77 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:5.96231604;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
78 d="m 369.14646,167.17616 0,-12.01507 c 0,-1.79719 -0.14219,-3.54961 -0.41278,-5.24208 -0.27057,-1.69246 -0.66936,-3.32456 -1.18362,-4.87804 -0.51426,-1.55348 -1.144,-3.02867 -1.87503,-4.40891 -0.73102,-1.38022 -1.56401,-2.66585 -2.48489,-3.83856 -0.92089,-1.17272 -1.92941,-2.23218 -3.01325,-3.16312 -1.08384,-0.93092 -2.24328,-1.73288 -3.46317,-2.38777 -1.21989,-0.65489 -2.50048,-1.16273 -3.82951,-1.50731 -1.32902,-0.34457 -2.70615,-0.52564 -4.11741,-0.52564 l -37.73887,0 c -1.41125,0 -2.78839,0.18107 -4.11741,0.52564 -1.32902,0.34458 -2.61065,0.85242 -3.83054,1.50731 -1.21989,0.65489 -2.37829,1.45685 -3.46214,2.38777 -1.08384,0.93094 -2.09236,1.9904 -3.01324,3.16312 -0.92089,1.17271 -1.75388,2.45834 -2.4849,3.83856 -0.73103,1.38024 -1.36076,2.85543 -1.87503,4.40891 -0.51425,1.55348 -0.91304,3.18558 -1.18362,4.87804 -0.27058,1.69247 -0.41277,3.44489 -0.41277,5.24208 l 0,12.01507 78.49818,0 z"
79 id="path8162"
80 inkscape:connector-curvature="0" />
81 <circle
82 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
83 id="circle8164"
84 cx="330.38046"
85 cy="99.067307"
86 r="22.437052" />
87 </g>
88 <g
89 id="g8166"
90 transform="matrix(0.45141696,0,0,0.45141696,132.51039,287.44622)">
91 <rect
92 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
93 id="rect8168"
94 width="162.79439"
95 height="116.67266"
96 x="75.449371"
97 y="61.817337"
98 ry="19.285713"
99 rx="19.285715" />
100 <path
101 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
102 d="m 91.037227,84.962934 107.225493,0"
103 id="path8170"
104 inkscape:connector-curvature="0" />
105 <path
106 inkscape:connector-curvature="0"
107 id="path8172"
108 d="m 91.037227,102.12532 63.768463,0"
109 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
110 <path
111 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
112 d="m 91.037227,119.28766 63.768463,0"
113 id="path8174"
114 inkscape:connector-curvature="0" />
115 <path
116 inkscape:connector-curvature="0"
117 id="path8176"
118 d="m 91.037227,136.45005 63.768463,0"
119 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
120 <rect
121 style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
122 id="rect8178"
123 width="12.753692"
124 height="49.597694"
125 x="165.66994"
126 y="107.16382"
127 rx="0"
128 ry="0" />
129 <rect
130 ry="0"
131 rx="0"
132 y="125.58582"
133 x="188.34315"
134 height="31.175694"
135 width="12.753692"
136 id="rect8180"
137 style="opacity:1;fill:#228b22;fill-opacity:1;fill-rule:evenodd;stroke:#228b22;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
138 <rect
139 style="opacity:1;fill:#4169e1;fill-opacity:1;fill-rule:evenodd;stroke:#4169e1;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
140 id="rect8182"
141 width="12.753692"
142 height="42.039951"
143 x="211.0164"
144 y="114.72157"
145 rx="0"
146 ry="0" />
147 </g>
148 </g>
149 </g>
150 </g>
151 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="slides-in-speaker-left.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="14.083333"
27 inkscape:cx="32"
28 inkscape:cy="24"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1592"
33 inkscape:window-height="856"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 transform="matrix(0.33773362,0,0,0.33773362,-194.55526,848.45843)"
60 id="g18859">
61 <g
62 transform="translate(502.92893,214.05327)"
63 id="g8275">
64 <g
65 id="g8158"
66 transform="translate(-188.89852,210.11173)">
67 <rect
68 rx="19.285715"
69 ry="19.285713"
70 y="50.17815"
71 x="275.3829"
72 height="116.67266"
73 width="162.79439"
74 id="rect8160"
75 style="opacity:1;fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
76 <path
77 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:5.96231604;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
78 d="m 419.18414,167.17616 0,-12.01507 c 0,-1.79719 -0.14219,-3.54961 -0.41278,-5.24208 -0.27057,-1.69246 -0.66936,-3.32456 -1.18362,-4.87804 -0.51426,-1.55348 -1.144,-3.02867 -1.87503,-4.40891 -0.73102,-1.38022 -1.56401,-2.66585 -2.48489,-3.83856 -0.92089,-1.17272 -1.92941,-2.23218 -3.01325,-3.16312 -1.08384,-0.93092 -2.24328,-1.73288 -3.46317,-2.38777 -1.21989,-0.65489 -2.50048,-1.16273 -3.82951,-1.50731 -1.32902,-0.34457 -2.70615,-0.52564 -4.11741,-0.52564 l -37.73887,0 c -1.41125,0 -2.78839,0.18107 -4.11741,0.52564 -1.32902,0.34458 -2.61065,0.85242 -3.83054,1.50731 -1.21989,0.65489 -2.37829,1.45685 -3.46214,2.38777 -1.08384,0.93094 -2.09236,1.9904 -3.01324,3.16312 -0.92089,1.17271 -1.75388,2.45834 -2.4849,3.83856 -0.73103,1.38024 -1.36076,2.85543 -1.87503,4.40891 -0.51425,1.55348 -0.91304,3.18558 -1.18362,4.87804 -0.27058,1.69247 -0.41277,3.44489 -0.41277,5.24208 l 0,12.01507 78.49818,0 z"
79 id="path8162"
80 inkscape:connector-curvature="0" />
81 <circle
82 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
83 id="circle8164"
84 cx="380.41815"
85 cy="99.067307"
86 r="22.437052" />
87 </g>
88 <g
89 id="g8166"
90 transform="matrix(0.45141696,0,0,0.45141696,132.51039,287.44622)">
91 <rect
92 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
93 id="rect8168"
94 width="162.79439"
95 height="116.67266"
96 x="-80.573112"
97 y="61.817337"
98 ry="19.285713"
99 rx="19.285715" />
100 <path
101 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
102 d="m -64.985255,84.962934 107.225493,0"
103 id="path8170"
104 inkscape:connector-curvature="0" />
105 <path
106 inkscape:connector-curvature="0"
107 id="path8172"
108 d="m -64.985255,102.12532 63.7684632,0"
109 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
110 <path
111 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
112 d="m -64.985255,119.28766 63.7684632,0"
113 id="path8174"
114 inkscape:connector-curvature="0" />
115 <path
116 inkscape:connector-curvature="0"
117 id="path8176"
118 d="m -64.985255,136.45005 63.7684632,0"
119 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
120 <rect
121 style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
122 id="rect8178"
123 width="12.753692"
124 height="49.597694"
125 x="9.6474552"
126 y="107.16382"
127 rx="0"
128 ry="0" />
129 <rect
130 ry="0"
131 rx="0"
132 y="125.58582"
133 x="32.320675"
134 height="31.175694"
135 width="12.753692"
136 id="rect8180"
137 style="opacity:1;fill:#228b22;fill-opacity:1;fill-rule:evenodd;stroke:#228b22;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
138 <rect
139 style="opacity:1;fill:#4169e1;fill-opacity:1;fill-rule:evenodd;stroke:#4169e1;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
140 id="rect8182"
141 width="12.753692"
142 height="42.039951"
143 x="54.993923"
144 y="114.72157"
145 rx="0"
146 ry="0" />
147 </g>
148 </g>
149 </g>
150 </g>
151 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="slides.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-41.27408"
28 inkscape:cy="-8.7808317"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1156"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 transform="matrix(0.34095025,0,0,0.34095025,-2.7414074,846.745)"
60 id="g18829">
61 <g
62 transform="translate(-54.950739,425.81729)"
63 id="g8122">
64 <rect
65 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
66 id="rect8068"
67 width="162.79439"
68 height="116.67266"
69 x="75.449371"
70 y="48.52586"
71 ry="19.285713"
72 rx="19.285715" />
73 <path
74 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
75 d="m 91.037227,71.671452 107.225493,0"
76 id="path8070"
77 inkscape:connector-curvature="0" />
78 <path
79 inkscape:connector-curvature="0"
80 id="path8072"
81 d="m 91.037227,88.833834 63.768463,0"
82 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
83 <path
84 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
85 d="m 91.037227,105.99619 63.768463,0"
86 id="path8074"
87 inkscape:connector-curvature="0" />
88 <path
89 inkscape:connector-curvature="0"
90 id="path8076"
91 d="m 91.037227,123.15858 63.768463,0"
92 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
93 <rect
94 style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
95 id="rect8078"
96 width="12.753692"
97 height="49.597694"
98 x="165.66994"
99 y="93.87233"
100 rx="0"
101 ry="0" />
102 <rect
103 ry="0"
104 rx="0"
105 y="112.29432"
106 x="188.34315"
107 height="31.175694"
108 width="12.753692"
109 id="rect8080"
110 style="opacity:1;fill:#228b22;fill-opacity:1;fill-rule:evenodd;stroke:#228b22;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
111 <rect
112 style="opacity:1;fill:#4169e1;fill-opacity:1;fill-rule:evenodd;stroke:#4169e1;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
113 id="rect8082"
114 width="12.753692"
115 height="42.039951"
116 x="211.0164"
117 y="101.43008"
118 rx="0"
119 ry="0" />
120 </g>
121 </g>
122 </g>
123 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="speaker-in-slides-left.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="14.083333"
27 inkscape:cx="32"
28 inkscape:cy="21.159763"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1592"
33 inkscape:window-height="856"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 transform="matrix(0.3379398,0,0,0.3379398,-130.60726,848.3486)"
60 id="g18846">
61 <g
62 transform="translate(497.06178,216.71571)"
63 id="g8290">
64 <g
65 id="g8132"
66 transform="translate(-172.73608,209.10158)">
67 <rect
68 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
69 id="rect8134"
70 width="162.79439"
71 height="116.67266"
72 x="75.449371"
73 y="48.52586"
74 ry="19.285713"
75 rx="19.285715" />
76 <path
77 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
78 d="m 118.46846,71.671452 107.2255,0"
79 id="path8136"
80 inkscape:connector-curvature="0" />
81 <path
82 inkscape:connector-curvature="0"
83 id="path8138"
84 d="m 161.9255,88.833834 63.76846,0"
85 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
86 <path
87 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
88 d="m 161.9255,105.99619 63.76846,0"
89 id="path8140"
90 inkscape:connector-curvature="0" />
91 <path
92 inkscape:connector-curvature="0"
93 id="path8142"
94 d="m 161.9255,123.15858 63.76846,0"
95 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
96 </g>
97 <g
98 id="g8150"
99 transform="matrix(0.45187084,0,0,0.45187084,-142.24163,286.23004)">
100 <rect
101 rx="19.285715"
102 ry="19.285713"
103 y="59.030243"
104 x="118.68226"
105 height="116.67266"
106 width="162.79439"
107 id="rect8152"
108 style="opacity:1;fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
109 <path
110 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
111 d="m 250.70696,175.70289 0,-11.80915 c 0,-1.76639 -0.17797,-3.48877 -0.51665,-5.15223 -0.33866,-1.66346 -0.8378,-3.26758 -1.48147,-4.79444 -0.64366,-1.52685 -1.43187,-2.97676 -2.34685,-4.33334 -0.91498,-1.35657 -1.95757,-2.62016 -3.11018,-3.77278 -1.15262,-1.15262 -2.41492,-2.19392 -3.7715,-3.1089 -1.35657,-0.91497 -2.80777,-1.70318 -4.33463,-2.34685 -1.52686,-0.64366 -3.12969,-1.1428 -4.79315,-1.48147 -1.66346,-0.33867 -3.38712,-0.51664 -5.15351,-0.51664 l -47.23535,0 c -1.76638,0 -3.49005,0.17797 -5.1535,0.51664 -1.66346,0.33867 -3.26759,0.83781 -4.79445,1.48147 -1.52686,0.64367 -2.97676,1.43188 -4.33334,2.34685 -1.35658,0.91498 -2.61888,1.95628 -3.77149,3.1089 -1.15262,1.15262 -2.19521,2.41621 -3.11019,3.77278 -0.91498,1.35658 -1.70318,2.80649 -2.34685,4.33334 -0.64366,1.52686 -1.1428,3.13098 -1.48147,4.79444 -0.33867,1.66346 -0.51664,3.38584 -0.51664,5.15223 l 0,11.80915 98.25122,0 z"
112 id="path8154"
113 inkscape:connector-curvature="0" />
114 <circle
115 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
116 id="circle8156"
117 cx="200.87273"
118 cy="107.91939"
119 r="22.437052" />
120 </g>
121 </g>
122 </g>
123 </g>
124 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="speaker-in-slides.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-41.27408"
28 inkscape:cy="-8.7808317"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1156"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 transform="matrix(0.3379398,0,0,0.3379398,-130.60726,848.3486)"
60 id="g18846">
61 <g
62 transform="translate(497.06178,216.71571)"
63 id="g8290">
64 <g
65 id="g8132"
66 transform="translate(-172.73608,209.10158)">
67 <rect
68 style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
69 id="rect8134"
70 width="162.79439"
71 height="116.67266"
72 x="75.449371"
73 y="48.52586"
74 ry="19.285713"
75 rx="19.285715" />
76 <path
77 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
78 d="m 91.037227,71.671452 107.225493,0"
79 id="path8136"
80 inkscape:connector-curvature="0" />
81 <path
82 inkscape:connector-curvature="0"
83 id="path8138"
84 d="m 91.037227,88.833834 63.768463,0"
85 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
86 <path
87 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
88 d="m 91.037227,105.99619 63.768463,0"
89 id="path8140"
90 inkscape:connector-curvature="0" />
91 <path
92 inkscape:connector-curvature="0"
93 id="path8142"
94 d="m 91.037227,123.15858 63.768463,0"
95 style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
96 </g>
97 <g
98 id="g8150"
99 transform="matrix(0.45187084,0,0,0.45187084,-142.24163,286.23004)">
100 <rect
101 rx="19.285715"
102 ry="19.285713"
103 y="59.030239"
104 x="275.3829"
105 height="116.67266"
106 width="162.79439"
107 id="rect8152"
108 style="opacity:1;fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
109 <path
110 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
111 d="m 407.4076,175.70289 0,-11.80915 c 0,-1.76639 -0.17797,-3.48877 -0.51665,-5.15223 -0.33866,-1.66346 -0.8378,-3.26758 -1.48147,-4.79444 -0.64366,-1.52685 -1.43187,-2.97676 -2.34685,-4.33334 -0.91498,-1.35657 -1.95757,-2.62016 -3.11018,-3.77278 -1.15262,-1.15262 -2.41492,-2.19392 -3.7715,-3.1089 -1.35657,-0.91497 -2.80777,-1.70318 -4.33463,-2.34685 -1.52686,-0.64366 -3.12969,-1.1428 -4.79315,-1.48147 -1.66346,-0.33867 -3.38712,-0.51664 -5.15351,-0.51664 l -47.23535,0 c -1.76638,0 -3.49005,0.17797 -5.1535,0.51664 -1.66346,0.33867 -3.26759,0.83781 -4.79445,1.48147 -1.52686,0.64367 -2.97676,1.43188 -4.33334,2.34685 -1.35658,0.91498 -2.61888,1.95628 -3.77149,3.1089 -1.15262,1.15262 -2.19521,2.41621 -3.11019,3.77278 -0.91498,1.35658 -1.70318,2.80649 -2.34685,4.33334 -0.64366,1.52686 -1.1428,3.13098 -1.48147,4.79444 -0.33867,1.66346 -0.51664,3.38584 -0.51664,5.15223 l 0,11.80915 98.25122,0 z"
112 id="path8154"
113 inkscape:connector-curvature="0" />
114 <circle
115 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
116 id="circle8156"
117 cx="357.57336"
118 cy="107.91939"
119 r="22.437052" />
120 </g>
121 </g>
122 </g>
123 </g>
124 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="speaker.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-41.27408"
28 inkscape:cy="-8.7808317"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1156"
34 inkscape:window-x="0"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <g
59 transform="matrix(0.33924969,0,0,0.33924969,-66.902839,847.65085)"
60 id="g18840">
61 <g
62 style="fill:#d3d3d3"
63 transform="translate(-65.246039,424.165)"
64 id="g8117">
65 <rect
66 rx="19.285715"
67 ry="19.285713"
68 y="50.17815"
69 x="275.3829"
70 height="116.67266"
71 width="162.79439"
72 id="rect8115"
73 style="opacity:1;fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
74 <path
75 style="opacity:1;fill:#e9967a;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
76 d="m 407.4076,166.85081 0,-11.80915 c 0,-1.76639 -0.17797,-3.48877 -0.51665,-5.15223 -0.33866,-1.66346 -0.8378,-3.26758 -1.48147,-4.79444 -0.64366,-1.52685 -1.43187,-2.97676 -2.34685,-4.33334 -0.91498,-1.35657 -1.95757,-2.62016 -3.11018,-3.77278 -1.15262,-1.15262 -2.41492,-2.19392 -3.7715,-3.1089 -1.35657,-0.91497 -2.80777,-1.70318 -4.33463,-2.34685 -1.52686,-0.64366 -3.12969,-1.1428 -4.79315,-1.48147 -1.66346,-0.33867 -3.38712,-0.51664 -5.15351,-0.51664 l -47.23535,0 c -1.76638,0 -3.49005,0.17797 -5.1535,0.51664 -1.66346,0.33867 -3.26759,0.83781 -4.79445,1.48147 -1.52686,0.64367 -2.97676,1.43188 -4.33334,2.34685 -1.35658,0.91498 -2.61888,1.95628 -3.77149,3.1089 -1.15262,1.15262 -2.19521,2.41621 -3.11019,3.77278 -0.91498,1.35658 -1.70318,2.80649 -2.34685,4.33334 -0.64366,1.52686 -1.1428,3.13098 -1.48147,4.79444 -0.33867,1.66346 -0.51664,3.38584 -0.51664,5.15223 l 0,11.80915 98.25122,0 z"
77 id="path8113"
78 inkscape:connector-curvature="0" />
79 <circle
80 style="opacity:1;fill:#ffe4b5;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
81 id="path8100"
82 cx="357.57336"
83 cy="99.067307"
84 r="22.437052" />
85 </g>
86 </g>
87 </g>
88 </svg>
88 xmlns="http://www.w3.org/2000/svg"
99 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
1010 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="32"
11 width="64"
12 height="48"
1313 id="svg3052"
1414 version="1.1"
15 inkscape:version="0.48.5 r10040"
16 sodipodi:docname="blank-stream.svg">
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="stream-live.svg">
1717 <defs
1818 id="defs3054" />
1919 <sodipodi:namedview
2323 borderopacity="1.0"
2424 inkscape:pageopacity="0.0"
2525 inkscape:pageshadow="2"
26 inkscape:zoom="22.4"
27 inkscape:cx="1.5807145"
28 inkscape:cy="15.895534"
26 inkscape:zoom="5.544571"
27 inkscape:cx="-41.27408"
28 inkscape:cy="-8.7808317"
2929 inkscape:document-units="px"
3030 inkscape:current-layer="layer1"
3131 showgrid="false"
32 inkscape:window-width="1920"
33 inkscape:window-height="1014"
32 inkscape:window-width="1912"
33 inkscape:window-height="1156"
3434 inkscape:window-x="0"
35 inkscape:window-y="27"
36 inkscape:window-maximized="1" />
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
3741 <metadata
3842 id="metadata3057">
3943 <rdf:RDF>
5054 inkscape:label="Layer 1"
5155 inkscape:groupmode="layer"
5256 id="layer1"
53 transform="translate(0,-1020.3622)">
54 <rect
55 style="color:#000000;fill:#00ff00;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.50000000000000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
56 id="rect3062"
57 width="30"
58 height="16.875"
59 x="1"
60 y="1027.9247" />
61 </g>
57 transform="translate(0,-1004.3622)">
58 <g
59 id="g18513"
60 transform="matrix(1.9062668,0,0,1.9062668,1.6714826,-948.37861)">
61 <g
62 style="fill:#00ff00"
63 transform="matrix(0.17779359,0,0,0.17779359,-84.293831,979.22779)"
64 id="g8261">
65 <g
66 style="fill:#00ff00"
67 id="g8240"
68 transform="translate(206.81577,216.25459)">
69 <rect
70 rx="19.285715"
71 ry="19.285713"
72 y="50.17815"
73 x="275.3829"
74 height="116.67266"
75 width="162.79439"
76 id="rect8242"
77 style="opacity:1;fill:#00ff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
78 </g>
79 <g
80 style="fill:#00ff00"
81 transform="matrix(0.78282213,0,0,1,423.47741,218.19296)"
82 id="g8210" />
83 <g
84 style="fill:#00ff00"
85 transform="matrix(0.45187084,0,0,0.45187084,457.78898,297.34173)"
86 id="g8222" />
87 </g>
88 <g
89 style="fill:none"
90 id="g18395"
91 transform="matrix(0.17779359,0,0,0.17779359,-84.293831,979.22779)">
92 <g
93 transform="translate(206.81577,216.25459)"
94 id="g18397"
95 style="fill:none">
96 <rect
97 style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:6.61302567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
98 id="rect18399"
99 width="162.79439"
100 height="116.67266"
101 x="275.3829"
102 y="50.17815"
103 ry="19.285713"
104 rx="19.285715" />
105 </g>
106 <g
107 style="fill:none"
108 id="g18401"
109 transform="matrix(0.78282213,0,0,1,423.47741,218.19296)" />
110 <g
111 style="fill:none"
112 id="g18403"
113 transform="matrix(0.45187084,0,0,0.45187084,457.78898,297.34173)" />
114 </g>
115 <g
116 id="text18475"
117 style="font-style:normal;font-weight:normal;font-size:14.67299652px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">
118 <path
119 inkscape:connector-curvature="0"
120 id="path18480"
121 style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:14.67295074px;line-height:125%;font-family:'Futura XBlkCnIt BT';-inkscape-font-specification:'Futura XBlkCnIt BT, Italic Condensed';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000"
122 d="m 3.5658706,1042.1711 1.6621702,-10.4602 2.9302914,0 -1.2466276,7.8667 2.1421935,0 -0.4083781,2.5935 -5.0796494,0 z" />
123 <path
124 inkscape:connector-curvature="0"
125 id="path18482"
126 style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:14.67295074px;line-height:125%;font-family:'Futura XBlkCnIt BT';-inkscape-font-specification:'Futura XBlkCnIt BT, Italic Condensed';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000"
127 d="m 9.5769087,1042.1711 1.6550053,-10.4602 2.923127,0 -1.66217,10.4602 -2.9159623,0 z" />
128 <path
129 inkscape:connector-curvature="0"
130 id="path18484"
131 style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:14.67295074px;line-height:125%;font-family:'Futura XBlkCnIt BT';-inkscape-font-specification:'Futura XBlkCnIt BT, Italic Condensed';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000"
132 d="m 14.764025,1031.7109 2.908798,0 0.01433,6.835 2.170852,-6.835 2.908798,0 -4.055122,10.4602 -3.209708,0 -0.737947,-10.4602 z" />
133 <path
134 inkscape:connector-curvature="0"
135 id="path18486"
136 style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:14.67295074px;line-height:125%;font-family:'Futura XBlkCnIt BT';-inkscape-font-specification:'Futura XBlkCnIt BT, Italic Condensed';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000"
137 d="m 21.620478,1042.1711 1.66217,-10.4602 5.179953,0 -0.401213,2.5363 -2.256826,0 -0.222101,1.3326 2.04189,0 -0.37972,2.4502 -2.04189,0 -0.236429,1.4401 2.26399,0 -0.429871,2.701 -5.179953,0 z" />
138 </g>
139 </g>
140 </g>
62141 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="64"
12 height="48"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.91 r13725"
16 sodipodi:docname="voc.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="7.8412075"
27 inkscape:cx="63.383292"
28 inkscape:cy="1.3797212"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1912"
33 inkscape:window-height="1156"
34 inkscape:window-x="1920"
35 inkscape:window-y="20"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1004.3622)">
58 <path
59 id="path35556"
60 style="fill:#808080;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
61 d="m 24.643146,1033.3174 13.58209,7.6519 -13.19949,7.3968 -0.31883,-6.5678 c 5.9613,-1.8476 3.01527,-4.8775 -0.12753,-5.994 z m -4.59113,14.6662 c -0.90862,-3.2786 -0.6333,-5.035 -0.38259,-6.823 -5.64662,-4.9021 1.45345,-10.5494 4.84619,-12.8169 -1.8931,-1.7981 -2.34997,-5.0135 -1.17883,-9.7665 -0.52092,-2.5775 0.61171,-5.1447 2.51791,-6.5575 0.76474,-0.054 3.08877,1.276 4.33607,4.6549 l 2.0405,0 c 1.46661,-4.1545 3.28393,-4.5157 4.39983,-4.5274 1.63677,0.8167 2.73822,4.27 2.27747,6.8182 1.19507,3.0334 0.72414,6.2858 -1.06592,9.1233 l 2.0405,2.0405 c 2.47267,0.1556 4.38795,-0.5875 3.63465,-6.4404 -0.60249,-4.681 4.14374,-3.606 4.33606,0.2551 0.19232,3.8611 0.2737,1.8317 -0.38259,6.1215 -0.6563,4.2898 -5.54762,4.9737 -5.54762,4.9737 1.3555,4.4521 1.565895,7.954 1.2425,12.509 -9.53333,6.1018 -20.44876,2.3885 -23.11413,0.4355 z m -9.91556,-11.5256 c 0.0667,-2.333 3.77941,-4.778 1.68979,-6.2651 -3.98273,-2.8343 2.98383,-7.7881 5.35632,-1.2115 1.45116,4.0226 -4.58254,8.2101 -4.08101,12.8806 -1.21066,-1.7643 -2.455133,-3.5229 -2.9651,-5.404 z m 44.16162,-8.2265 c 0,12.7528 -10.33814,23.0909 -23.09088,23.0909 -12.75274,0 -23.09088,-10.3381 -23.09088,-23.0909 0,-12.7527 10.33814,-23.0909 23.09088,-23.0909 12.75274,0 23.09088,10.3382 23.09088,23.0909 z"
62 inkscape:connector-curvature="0"
63 sodipodi:nodetypes="cccccccccccccccccsssccccssccsssss" />
64 </g>
65 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 width="32"
12 height="24"
13 id="svg3052"
14 version="1.1"
15 inkscape:version="0.92.3 (2405546, 2018-03-11)"
16 sodipodi:docname="voc2.svg">
17 <defs
18 id="defs3054" />
19 <sodipodi:namedview
20 id="base"
21 pagecolor="#ffffff"
22 bordercolor="#666666"
23 borderopacity="1.0"
24 inkscape:pageopacity="0.0"
25 inkscape:pageshadow="2"
26 inkscape:zoom="2.7722855"
27 inkscape:cx="70.671935"
28 inkscape:cy="-13.486669"
29 inkscape:document-units="px"
30 inkscape:current-layer="layer1"
31 showgrid="false"
32 inkscape:window-width="1592"
33 inkscape:window-height="871"
34 inkscape:window-x="0"
35 inkscape:window-y="0"
36 inkscape:window-maximized="0"
37 showguides="true"
38 inkscape:guide-bbox="true"
39 inkscape:snap-to-guides="true"
40 inkscape:snap-global="true" />
41 <metadata
42 id="metadata3057">
43 <rdf:RDF>
44 <cc:Work
45 rdf:about="">
46 <dc:format>image/svg+xml</dc:format>
47 <dc:type
48 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
49 <dc:title></dc:title>
50 </cc:Work>
51 </rdf:RDF>
52 </metadata>
53 <g
54 inkscape:label="Layer 1"
55 inkscape:groupmode="layer"
56 id="layer1"
57 transform="translate(0,-1028.3622)">
58 <path
59 id="path35556"
60 style="fill:#808080;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
61 d="m 12.871038,1042.7866 6.474333,3.6475 -6.291954,3.5259 -0.151981,-3.1308 c 2.841642,-0.8807 1.437324,-2.325 -0.06079,-2.8572 z m -2.188507,6.9911 c -0.433123,-1.5629 -0.301883,-2.4001 -0.182374,-3.2524 -2.6916401,-2.3368 0.692833,-5.0287 2.31009,-6.1096 -0.902406,-0.8571 -1.120188,-2.3899 -0.561927,-4.6555 -0.248313,-1.2287 0.291591,-2.4524 1.200242,-3.1259 0.364537,-0.026 1.47236,0.6083 2.066925,2.2189 h 0.972668 c 0.699107,-1.9803 1.56539,-2.1525 2.097319,-2.1581 0.780218,0.3893 1.305259,2.0354 1.085628,3.2501 0.569668,1.446 0.345184,2.9963 -0.508105,4.3489 l 0.972669,0.9727 c 1.178676,0.074 2.091667,-0.2801 1.732571,-3.07 -0.09155,-0.7113 -0.73139,-2.4632 -0.31274,-2.5717 0.722385,-0.1871 0.812848,2.0387 1.026537,2.0821 0.252397,0.051 0.23183,-2.2501 0.920265,-2.0407 0.758449,0.2307 -0.13345,1.8818 -0.215767,2.3067 0.634076,-0.1035 0.639473,0.1614 0.648625,0.3452 0.09167,1.8405 0.130468,0.8731 -0.182374,2.918 -0.312846,2.0449 -2.644448,2.3709 -2.644448,2.3709 0.646142,2.1222 0.746433,3.7915 0.592277,5.9628 -4.544363,2.9086 -9.747548,1.1385 -11.018081,0.2076 z m -4.7265654,-5.4941 c 0.031795,-1.1121 1.8015753,-2.2776 0.8054918,-2.9864 -1.8984942,-1.3511 1.4223366,-3.7125 2.5532595,-0.5775 0.6917411,1.9175 -2.184413,3.9136 -1.945343,6.1399 -0.577099,-0.841 -1.1703164,-1.6793 -1.4134083,-2.576 z m 21.0510324,-3.9214 c 0,6.079 -4.928001,11.007 -11.006998,11.007 -6.0789971,0 -11.006998,-4.928 -11.006998,-11.007 0,-6.079 4.9280009,-11.007 11.006998,-11.007 6.078997,0 11.006998,4.928 11.006998,11.007 z"
62 inkscape:connector-curvature="0"
63 sodipodi:nodetypes="cccccccccccccccccsssscssccccssccsssss" />
64 </g>
65 </svg>
0 .btn_grp_a:checked {
1 background-image: none;
2 background-color: blue;
3 color: white;
0 #mixer {
1 font-family: roboto condensed;
42 }
53
6 .btn_grp_b:checked {
4 #mixer .name {
5 font-size: 1.0em;
6 }
7
8 #mixer button {
9 font-size: 20px;
10 border-radius: 15px;
11 background-color: #505050;
12 margin-left:0.3em;
13 margin-top:0.3em;
14 margin-bottom:0.3em;
15 margin-right:0.3em;
716 background-image: none;
8 background-color: red;
9 color: white;
10 }
17 }
18
19 #mixer button:disabled {
20 background-color: #303030;
21 }
22
23 #mixer label:disabled {
24 color: #505050;
25 }
26
27 #mixer button:checked, #mixer button:active {
28 background-image: none;
29 background-color: #ddbb00;
30 color: #000000;
31 }
32
33 #mixer button:checked:disabled {
34 background-color: #504530
35 }
36
37 #mixer button:checked:disabled label {
38 color: #302510;
39 }
40
41 #mixer button:checked label:backdrop {
42 color: #997700;
43 }
44
45 #mixer button:checked:disabled label:backdrop {
46 color: #302510;
47 }
48
49 #preview_b button:checked:disabled {
50 background-color: #303030
51 }
52
53 #preview_b button:checked:disabled label {
54 color: #505050;
55 }
56
57 #preview_b button:checked:disabled label:backdrop {
58 color: #505050;
59 }
60
61 #mixer .mode:disabledlabel:backdrop {
62 color: #106610;
63 }
64
65 #mixer .mode.blink button:checked {
66 background-color: #FF0000;
67 color: #FFFFFF;
68 border-color: #FF8080;
69 box-shadow: 0 0 0.3em #FF8080;
70 text-shadow: 0 0 0.3em #FFFFFF;
71 }
72
73 #mixer .mode button:checked label:backdrop {
74 color: #CCAA80;
75 }
76
77 #mixer .mode.blink button:checked label:backdrop {
78 color: #FFFFFF;
79 }
80
81 #mixer .mode button:checked {
82 background-color: #6B0002;
83 color: #CCAA80;
84 }
85
86 #mixer .mode.live button:checked {
87 background-color: #208020;
88 color: #80FF80;
89 border-color: #80FF80;
90 box-shadow: 0 0 0.3em #80FF80;
91 text-shadow: 0 0 0.3em #FFFFFF;
92 }
93
94 #mixer .mode.live button:checked label:backdrop {
95 color: #80FF80;
96 }
97
98 #mixer button:checked.option {
99 background-color: #C06000;
100 color: #FFDD80;
101 }
102
103 #mixer button:checked.option label:backdrop {
104 color: #FFDD80;
105 }
106
107 #queues { font-size: 1.0em; }
108
109 toolbar {
110 background-color:rgba(0,0,0,0);
111 }
112
113 #mixer .glow button {
114 box-shadow: 0 0 0.3em #ddbb00;
115 text-shadow: 0 0 0.3em #ddbb00;
116 color: #ffee80;
117 border-color: #ddbb00;
118 }
119
120 #mixer .glow button:disabled {
121 text-shadow: 0 0 0.3em #403010;
122 }
123
124 #mixer .glow button:disabled label {
125 color: #605040;
126 }
127
128 #mixer .glow button label:backdrop {
129 color: #ddbb00;
130 }
131
132 #mixer .glow button:checked label:backdrop {
133 color: #ffee80;
134 }
135
136 .notebook {
137 box-shadow: 0 0 0.3em rgba(0,0,0,128);
138 }
139
140 #mixer frame border {
141 background-color:rgba(37,42,44,0);
142 border-left-color:rgba(74,84,88,0);
143 border-right-color:rgba(74,84,88,0);
144 border-top-color:rgb(74,84,88);
145 border-bottom-color:rgba(74,84,88,0);
146 }
147
148 #mixer frame > label {
149 margin-left: 1em;
150 margin-right: 1em;
151 }
00 <?xml version="1.0" encoding="UTF-8"?>
1 <!-- Generated with glade 3.20.0 -->
1 <!-- Generated with glade 3.22.1 -->
22 <interface>
3 <requires lib="gtk+" version="3.4"/>
4 <object class="GtkImage" id="img-blank-stream">
3 <requires lib="gtk+" version="3.10"/>
4 <object class="GtkImage" id="img-logo">
55 <property name="visible">True</property>
66 <property name="can_focus">False</property>
7 <property name="pixbuf">blank-stream.svg</property>
7 <property name="pixbuf">logo.svg</property>
88 </object>
9 <object class="GtkImage" id="img-composite-fullscreen">
9 <object class="GtkImage" id="img-ports">
1010 <property name="visible">True</property>
1111 <property name="can_focus">False</property>
12 <property name="pixbuf">composite-fullscreen.svg</property>
12 <property name="tooltip_text" translatable="yes">Show TCP ports of inputs and outputs</property>
13 <property name="pixbuf">ports.svg</property>
1314 </object>
14 <object class="GtkImage" id="img-composite-picture-in-picture">
15 <object class="GtkImage" id="img-queues">
1516 <property name="visible">True</property>
1617 <property name="can_focus">False</property>
17 <property name="pixbuf">composite-picture-in-picture.svg</property>
18 <property name="tooltip_text" translatable="yes">Show queues' filling levels</property>
19 <property name="pixbuf">queues.svg</property>
1820 </object>
19 <object class="GtkImage" id="img-composite-side-by-side-equal">
21 <object class="GtkImage" id="img-voc">
2022 <property name="visible">True</property>
2123 <property name="can_focus">False</property>
22 <property name="pixbuf">composite-side-by-side-equal.svg</property>
24 <property name="tooltip_text" translatable="yes">VOC2MIX - VOC2GUI
25
26 Full-HD Software Live-Video-Mixer in python
27
28 To get in touch with us we'd ask to join #voctomix on the hackint IRC network, mail to voc AT c3voc DOT de or meet us on one of the many conferences we're at. You may also want to watch MaZderMinds talk about Voctomix and a presentation some of us gave about C3VOCs video infrastructure.
29
30 https://c3voc.de/
31 https://github.com/voc/voctomix </property>
32 <property name="pixbuf">voc2.svg</property>
2333 </object>
24 <object class="GtkImage" id="img-composite-side-by-side-preview">
25 <property name="visible">True</property>
26 <property name="can_focus">False</property>
27 <property name="pixbuf">composite-side-by-side-preview.svg</property>
34 <object class="GtkListStore" id="insert-store">
35 <columns>
36 <!-- column-name id -->
37 <column type="gchararray"/>
38 <!-- column-name text -->
39 <column type="gchararray"/>
40 </columns>
2841 </object>
29 <object class="GtkImage" id="img-stream-live">
30 <property name="visible">True</property>
31 <property name="can_focus">False</property>
32 <property name="pixbuf">stream-live.svg</property>
42 <object class="GtkListStore" id="ports_store">
43 <columns>
44 <!-- column-name name -->
45 <column type="gchararray"/>
46 <!-- column-name audio -->
47 <column type="gint"/>
48 <!-- column-name video -->
49 <column type="gint"/>
50 <!-- column-name io -->
51 <column type="gchararray"/>
52 <!-- column-name port -->
53 <column type="gchararray"/>
54 <!-- column-name color -->
55 <column type="gchararray"/>
56 <!-- column-name bgcolor -->
57 <column type="gchararray"/>
58 </columns>
59 </object>
60 <object class="GtkListStore" id="queue_store">
61 <columns>
62 <!-- column-name Name -->
63 <column type="gchararray"/>
64 <!-- column-name Delay -->
65 <column type="gfloat"/>
66 </columns>
3367 </object>
3468 <object class="GtkWindow" id="window">
69 <property name="name">main</property>
3570 <property name="can_focus">False</property>
71 <property name="title" translatable="yes">Voctomix GUI</property>
3672 <property name="default_width">1280</property>
3773 <property name="default_height">720</property>
38 <property name="title" translatable="yes">Voctomix GUI</property>
74 <property name="icon">voc.svg</property>
3975 <child>
40 <object class="GtkBox" id="box_toolbar_content">
76 <placeholder/>
77 </child>
78 <child>
79 <object class="GtkBox" id="box_main">
4180 <property name="visible">True</property>
4281 <property name="can_focus">False</property>
43 <property name="orientation">vertical</property>
4482 <child>
45 <object class="GtkToolbar" id="toolbar">
83 <object class="GtkBox" id="preview_box">
4684 <property name="visible">True</property>
4785 <property name="can_focus">False</property>
48 <property name="hexpand">True</property>
49 <property name="toolbar_style">both</property>
86 <property name="orientation">vertical</property>
5087 <child>
51 <object class="GtkRadioToolButton" id="composite-fullscreen">
52 <property name="visible">True</property>
53 <property name="can_focus">False</property>
54 <property name="label" translatable="yes">Fullscreen</property>
55 <property name="use_underline">True</property>
56 <property name="icon_widget">img-composite-fullscreen</property>
57 </object>
58 <packing>
59 <property name="expand">False</property>
60 <property name="homogeneous">True</property>
61 </packing>
88 <placeholder/>
6289 </child>
63 <child>
64 <object class="GtkRadioToolButton" id="composite-picture-in-picture">
65 <property name="visible">True</property>
66 <property name="can_focus">False</property>
67 <property name="label" translatable="yes">Picture in Picture</property>
68 <property name="use_underline">True</property>
69 <property name="icon_widget">img-composite-picture-in-picture</property>
70 <property name="group">composite-fullscreen</property>
71 </object>
72 <packing>
73 <property name="expand">False</property>
74 <property name="homogeneous">True</property>
75 </packing>
76 </child>
77 <child>
78 <object class="GtkRadioToolButton" id="composite-side-by-side-equal">
79 <property name="visible">True</property>
80 <property name="can_focus">False</property>
81 <property name="label" translatable="yes">Side-by-side Equal</property>
82 <property name="use_underline">True</property>
83 <property name="icon_widget">img-composite-side-by-side-equal</property>
84 <property name="group">composite-fullscreen</property>
85 </object>
86 <packing>
87 <property name="expand">False</property>
88 <property name="homogeneous">True</property>
89 </packing>
90 </child>
91 <child>
92 <object class="GtkRadioToolButton" id="composite-side-by-side-preview">
93 <property name="visible">True</property>
94 <property name="can_focus">False</property>
95 <property name="label" translatable="yes">Side-by-side Preview</property>
96 <property name="use_underline">True</property>
97 <property name="icon_widget">img-composite-side-by-side-preview</property>
98 <property name="group">composite-fullscreen</property>
99 </object>
100 <packing>
101 <property name="expand">False</property>
102 <property name="homogeneous">True</property>
103 </packing>
104 </child>
105 <child>
106 <object class="GtkSeparatorToolItem" id="s1">
107 <property name="visible">True</property>
108 <property name="can_focus">False</property>
109 </object>
110 <packing>
111 <property name="expand">False</property>
112 <property name="homogeneous">True</property>
113 </packing>
114 </child>
115 <child>
116 <object class="GtkRadioToolButton" id="stream_blank">
117 <property name="visible">True</property>
118 <property name="can_focus">False</property>
119 <property name="label" translatable="yes">Blank Stream</property>
120 <property name="use_underline">True</property>
121 <property name="icon_widget">img-blank-stream</property>
122 <property name="group">stream_live</property>
123 </object>
124 <packing>
125 <property name="expand">False</property>
126 <property name="homogeneous">True</property>
127 </packing>
128 </child>
129 <child>
130 <object class="GtkRadioToolButton" id="stream_live">
131 <property name="visible">True</property>
132 <property name="can_focus">False</property>
133 <property name="label" translatable="yes">Stream Live</property>
134 <property name="use_underline">True</property>
135 <property name="icon_widget">img-stream-live</property>
136 <property name="active">True</property>
137 </object>
138 <packing>
139 <property name="expand">False</property>
140 <property name="homogeneous">True</property>
141 </packing>
142 </child>
143 <child>
144 <object class="GtkToolButton" id="cut">
145 <property name="visible">True</property>
146 <property name="can_focus">False</property>
147 <property name="label" translatable="yes">Cut</property>
148 <property name="use_underline">True</property>
149 <property name="stock_id">gtk-cut</property>
150 <property name="no_show_all">True</property>
151 </object>
152 <packing>
153 <property name="expand">False</property>
154 <property name="homogeneous">True</property>
155 </packing>
156 </child>
157 <child>
158 <object class="GtkSeparatorToolItem" id="s2">
159 <property name="visible">True</property>
160 <property name="can_focus">False</property>
161 <property name="draw">False</property>
162 </object>
163 <packing>
164 <property name="expand">True</property>
165 <property name="homogeneous">True</property>
166 </packing>
167 </child>
168 <child>
169 <object class="StudioClock" id="studioclock">
170 <property name="visible">True</property>
171 <property name="can_focus">False</property>
172 </object>
173 <packing>
174 <property name="expand">True</property>
175 <property name="homogeneous">True</property>
176 </packing>
177 </child>
178 <child>
179 <object class="GtkToolButton" id="close">
180 <property name="visible">True</property>
181 <property name="can_focus">False</property>
182 <property name="label" translatable="yes">Close</property>
183 <property name="use_underline">True</property>
184 <property name="stock_id">gtk-close</property>
185 <property name="no_show_all">True</property>
186 </object>
187 <packing>
188 <property name="expand">False</property>
189 <property name="homogeneous">True</property>
190 </packing>
191 </child>
192 <style>
193 <class name="primary-toolbar"/>
194 </style>
19590 </object>
19691 <packing>
19792 <property name="expand">False</property>
20095 </packing>
20196 </child>
20297 <child>
203 <object class="GtkBox" id="box_left_right">
98 <object class="GtkBox" id="box_mix">
20499 <property name="visible">True</property>
205100 <property name="can_focus">False</property>
101 <property name="orientation">vertical</property>
206102 <child>
207 <object class="GtkBox" id="box_left">
103 <object class="GtkBox" id="box_output">
208104 <property name="visible">True</property>
209105 <property name="can_focus">False</property>
210 <property name="orientation">vertical</property>
211106 <child>
212 <placeholder/>
107 <object class="GtkAspectFrame" id="output_aspect_ratio">
108 <property name="visible">True</property>
109 <property name="can_focus">False</property>
110 <property name="label_xalign">0</property>
111 <property name="shadow_type">none</property>
112 <property name="obey_child">False</property>
113 <child>
114 <object class="GtkDrawingArea" id="video_main">
115 <property name="width_request">100</property>
116 <property name="visible">True</property>
117 <property name="can_focus">False</property>
118 <property name="double_buffered">False</property>
119 <property name="hexpand">True</property>
120 <property name="vexpand">True</property>
121 </object>
122 </child>
123 </object>
124 <packing>
125 <property name="expand">False</property>
126 <property name="fill">True</property>
127 <property name="position">0</property>
128 </packing>
129 </child>
130 <child>
131 <object class="GtkBox" id="info_win">
132 <property name="visible">True</property>
133 <property name="can_focus">False</property>
134 <property name="orientation">vertical</property>
135 <property name="homogeneous">True</property>
136 <child>
137 <object class="GtkBox" id="ports_win">
138 <property name="can_focus">False</property>
139 <property name="no_show_all">True</property>
140 <property name="orientation">vertical</property>
141 <child>
142 <object class="GtkHeaderBar" id="ports_title">
143 <property name="visible">True</property>
144 <property name="can_focus">False</property>
145 <property name="title" translatable="yes">Ports</property>
146 <property name="subtitle" translatable="yes">Input/Output Ports</property>
147 </object>
148 <packing>
149 <property name="expand">False</property>
150 <property name="fill">True</property>
151 <property name="position">0</property>
152 </packing>
153 </child>
154 <child>
155 <object class="GtkScrolledWindow" id="ports_scroll">
156 <property name="visible">True</property>
157 <property name="can_focus">False</property>
158 <property name="vexpand">True</property>
159 <property name="hscrollbar_policy">never</property>
160 <property name="shadow_type">in</property>
161 <property name="propagate_natural_width">True</property>
162 <child>
163 <object class="GtkTreeView" id="ports_tree">
164 <property name="name">queues</property>
165 <property name="visible">True</property>
166 <property name="can_focus">False</property>
167 <property name="model">ports_store</property>
168 <property name="search_column">1</property>
169 <property name="enable_grid_lines">both</property>
170 <child internal-child="selection">
171 <object class="GtkTreeSelection">
172 <property name="mode">none</property>
173 </object>
174 </child>
175 <child>
176 <object class="GtkTreeViewColumn">
177 <property name="title" translatable="yes">Name</property>
178 <property name="clickable">True</property>
179 <property name="sort_indicator">True</property>
180 <property name="sort_column_id">0</property>
181 <child>
182 <object class="GtkCellRendererText">
183 <property name="weight">800</property>
184 </object>
185 <attributes>
186 <attribute name="background">6</attribute>
187 <attribute name="foreground">5</attribute>
188 <attribute name="text">0</attribute>
189 </attributes>
190 </child>
191 </object>
192 </child>
193 <child>
194 <object class="GtkTreeViewColumn">
195 <property name="sizing">fixed</property>
196 <property name="title" translatable="yes">A</property>
197 <property name="sort_column_id">1</property>
198 <child>
199 <object class="GtkCellRendererText"/>
200 <attributes>
201 <attribute name="background">6</attribute>
202 <attribute name="foreground">5</attribute>
203 <attribute name="text">1</attribute>
204 </attributes>
205 </child>
206 </object>
207 </child>
208 <child>
209 <object class="GtkTreeViewColumn">
210 <property name="sizing">fixed</property>
211 <property name="title" translatable="yes">V</property>
212 <property name="sort_column_id">2</property>
213 <child>
214 <object class="GtkCellRendererText"/>
215 <attributes>
216 <attribute name="background">6</attribute>
217 <attribute name="foreground">5</attribute>
218 <attribute name="text">2</attribute>
219 </attributes>
220 </child>
221 </object>
222 </child>
223 <child>
224 <object class="GtkTreeViewColumn">
225 <property name="sizing">fixed</property>
226 <property name="title" translatable="yes">I/O</property>
227 <property name="sort_column_id">3</property>
228 <child>
229 <object class="GtkCellRendererText"/>
230 <attributes>
231 <attribute name="background">6</attribute>
232 <attribute name="foreground">5</attribute>
233 <attribute name="text">3</attribute>
234 </attributes>
235 </child>
236 </object>
237 </child>
238 <child>
239 <object class="GtkTreeViewColumn">
240 <property name="title" translatable="yes">Port</property>
241 <property name="clickable">True</property>
242 <property name="sort_indicator">True</property>
243 <property name="sort_order">descending</property>
244 <property name="sort_column_id">4</property>
245 <child>
246 <object class="GtkCellRendererText">
247 <property name="wrap_width">100</property>
248 </object>
249 <attributes>
250 <attribute name="background">6</attribute>
251 <attribute name="foreground">5</attribute>
252 <attribute name="text">4</attribute>
253 </attributes>
254 </child>
255 </object>
256 </child>
257 </object>
258 </child>
259 </object>
260 <packing>
261 <property name="expand">False</property>
262 <property name="fill">True</property>
263 <property name="position">1</property>
264 </packing>
265 </child>
266 </object>
267 <packing>
268 <property name="expand">False</property>
269 <property name="fill">True</property>
270 <property name="position">0</property>
271 </packing>
272 </child>
273 <child>
274 <object class="GtkBox" id="queue_win">
275 <property name="can_focus">False</property>
276 <property name="no_show_all">True</property>
277 <property name="orientation">vertical</property>
278 <child>
279 <object class="GtkHeaderBar">
280 <property name="visible">True</property>
281 <property name="can_focus">False</property>
282 <property name="title" translatable="yes">Delay Diagnosis</property>
283 <property name="subtitle" translatable="yes">Queue filling levels in seconds</property>
284 </object>
285 <packing>
286 <property name="expand">False</property>
287 <property name="fill">True</property>
288 <property name="position">0</property>
289 </packing>
290 </child>
291 <child>
292 <object class="GtkScrolledWindow" id="queue_scroll">
293 <property name="visible">True</property>
294 <property name="can_focus">False</property>
295 <property name="vexpand">True</property>
296 <property name="hscrollbar_policy">never</property>
297 <property name="shadow_type">in</property>
298 <property name="propagate_natural_width">True</property>
299 <child>
300 <object class="GtkTreeView" id="queue_tree">
301 <property name="name">queues</property>
302 <property name="visible">True</property>
303 <property name="can_focus">False</property>
304 <property name="model">queue_store</property>
305 <property name="search_column">1</property>
306 <property name="enable_grid_lines">both</property>
307 <child internal-child="selection">
308 <object class="GtkTreeSelection"/>
309 </child>
310 <child>
311 <object class="GtkTreeViewColumn">
312 <property name="title" translatable="yes">Name</property>
313 <property name="sort_indicator">True</property>
314 <property name="sort_column_id">0</property>
315 <child>
316 <object class="GtkCellRendererText"/>
317 <attributes>
318 <attribute name="text">0</attribute>
319 </attributes>
320 </child>
321 </object>
322 </child>
323 <child>
324 <object class="GtkTreeViewColumn">
325 <property name="title" translatable="yes">Delay</property>
326 <property name="sort_indicator">True</property>
327 <property name="sort_order">descending</property>
328 <property name="sort_column_id">1</property>
329 <child>
330 <object class="GtkCellRendererText"/>
331 <attributes>
332 <attribute name="text">1</attribute>
333 </attributes>
334 </child>
335 </object>
336 </child>
337 </object>
338 </child>
339 </object>
340 <packing>
341 <property name="expand">False</property>
342 <property name="fill">True</property>
343 <property name="position">1</property>
344 </packing>
345 </child>
346 </object>
347 <packing>
348 <property name="expand">False</property>
349 <property name="fill">True</property>
350 <property name="position">1</property>
351 </packing>
352 </child>
353 </object>
354 <packing>
355 <property name="expand">False</property>
356 <property name="fill">True</property>
357 <property name="position">2</property>
358 </packing>
359 </child>
360 </object>
361 <packing>
362 <property name="expand">True</property>
363 <property name="fill">True</property>
364 <property name="position">0</property>
365 </packing>
366 </child>
367 <child>
368 <object class="GtkBox" id="box_mixer_panel">
369 <property name="name">mixer</property>
370 <property name="visible">True</property>
371 <property name="can_focus">False</property>
372 <property name="margin_top">6</property>
373 <property name="margin_bottom">6</property>
374 <property name="spacing">6</property>
375 <child>
376 <object class="GtkLabel">
377 <property name="visible">True</property>
378 <property name="can_focus">False</property>
379 <property name="hexpand">True</property>
380 </object>
381 <packing>
382 <property name="expand">True</property>
383 <property name="fill">True</property>
384 <property name="position">0</property>
385 </packing>
386 </child>
387 <child>
388 <object class="GtkBox">
389 <property name="visible">True</property>
390 <property name="can_focus">False</property>
391 <property name="spacing">12</property>
392 <child>
393 <object class="GtkNotebook">
394 <property name="visible">True</property>
395 <property name="can_focus">False</property>
396 <property name="tab_pos">bottom</property>
397 <property name="show_border">False</property>
398 <child>
399 <object class="GtkBox">
400 <property name="visible">True</property>
401 <property name="can_focus">False</property>
402 <property name="orientation">vertical</property>
403 <child>
404 <object class="GtkFrame" id="box_blinds">
405 <property name="visible">True</property>
406 <property name="can_focus">False</property>
407 <property name="margin_left">6</property>
408 <property name="margin_right">6</property>
409 <property name="margin_top">6</property>
410 <property name="margin_bottom">6</property>
411 <property name="label_xalign">0.5</property>
412 <property name="shadow_type">in</property>
413 <child>
414 <object class="GtkAlignment">
415 <property name="visible">True</property>
416 <property name="can_focus">False</property>
417 <property name="xalign">0</property>
418 <property name="bottom_padding">6</property>
419 <property name="left_padding">4</property>
420 <property name="right_padding">4</property>
421 <child>
422 <object class="GtkToolbar" id="toolbar_blinder">
423 <property name="visible">True</property>
424 <property name="can_focus">False</property>
425 <property name="orientation">vertical</property>
426 <property name="toolbar_style">text</property>
427 <property name="show_arrow">False</property>
428 <child>
429 <object class="GtkRadioToolButton" id="stream_live">
430 <property name="name">live</property>
431 <property name="visible">True</property>
432 <property name="can_focus">False</property>
433 <property name="tooltip_text" translatable="yes">Put output on air</property>
434 <property name="label" translatable="yes">LIVE</property>
435 <property name="use_underline">True</property>
436 <property name="active">True</property>
437 <style>
438 <class name="mode"/>
439 <class name="live"/>
440 </style>
441 </object>
442 <packing>
443 <property name="expand">True</property>
444 <property name="homogeneous">True</property>
445 </packing>
446 </child>
447 <child>
448 <object class="GtkRadioToolButton" id="stream_blind">
449 <property name="visible">True</property>
450 <property name="can_focus">False</property>
451 <property name="tooltip_text" translatable="yes">Put pause stream on air</property>
452 <property name="label" translatable="yes">PAUSE</property>
453 <property name="use_underline">True</property>
454 <property name="active">True</property>
455 <property name="group">stream_live</property>
456 <style>
457 <class name="mode"/>
458 </style>
459 </object>
460 <packing>
461 <property name="expand">True</property>
462 <property name="homogeneous">True</property>
463 </packing>
464 </child>
465 <style>
466 <class name="secondary-toolbar"/>
467 </style>
468 </object>
469 </child>
470 </object>
471 </child>
472 <child type="label">
473 <object class="GtkLabel">
474 <property name="visible">True</property>
475 <property name="can_focus">False</property>
476 <property name="label" translatable="yes">BLINDS</property>
477 </object>
478 </child>
479 </object>
480 <packing>
481 <property name="expand">False</property>
482 <property name="fill">True</property>
483 <property name="position">0</property>
484 </packing>
485 </child>
486 <child>
487 <object class="GtkFrame">
488 <property name="visible">True</property>
489 <property name="can_focus">False</property>
490 <property name="margin_left">6</property>
491 <property name="margin_right">6</property>
492 <property name="margin_bottom">4</property>
493 <property name="label_xalign">0.5</property>
494 <property name="shadow_type">in</property>
495 <child>
496 <object class="GtkAlignment">
497 <property name="visible">True</property>
498 <property name="can_focus">False</property>
499 <property name="top_padding">4</property>
500 <property name="bottom_padding">0</property>
501 <property name="left_padding">4</property>
502 <property name="right_padding">4</property>
503 <child>
504 <object class="StudioClock" id="studioclock">
505 <property name="visible">True</property>
506 <property name="can_focus">False</property>
507 <property name="margin_bottom">6</property>
508 </object>
509 </child>
510 </object>
511 </child>
512 <child type="label">
513 <object class="GtkLabel">
514 <property name="visible">True</property>
515 <property name="can_focus">False</property>
516 <property name="label" translatable="yes">CLOCK</property>
517 </object>
518 </child>
519 </object>
520 <packing>
521 <property name="expand">True</property>
522 <property name="fill">True</property>
523 <property name="position">1</property>
524 </packing>
525 </child>
526 </object>
527 </child>
528 <child type="tab">
529 <object class="GtkLabel">
530 <property name="visible">True</property>
531 <property name="can_focus">False</property>
532 <property name="label" translatable="yes">STREAM</property>
533 </object>
534 <packing>
535 <property name="tab_fill">False</property>
536 </packing>
537 </child>
538 <style>
539 <class name="notebook"/>
540 </style>
541 </object>
542 <packing>
543 <property name="expand">False</property>
544 <property name="fill">True</property>
545 <property name="position">0</property>
546 </packing>
547 </child>
548 <child>
549 <object class="GtkNotebook">
550 <property name="visible">True</property>
551 <property name="can_focus">False</property>
552 <property name="tab_pos">bottom</property>
553 <property name="show_border">False</property>
554 <child>
555 <object class="GtkBox">
556 <property name="visible">True</property>
557 <property name="can_focus">False</property>
558 <property name="margin_left">6</property>
559 <property name="margin_right">6</property>
560 <property name="margin_top">6</property>
561 <property name="margin_bottom">6</property>
562 <property name="spacing">6</property>
563 <child>
564 <object class="GtkBox">
565 <property name="visible">True</property>
566 <property name="can_focus">False</property>
567 <property name="orientation">vertical</property>
568 <property name="spacing">6</property>
569 <child>
570 <object class="GtkBox" id="box_preview">
571 <property name="visible">True</property>
572 <property name="can_focus">False</property>
573 <property name="spacing">6</property>
574 <child>
575 <object class="GtkLabel">
576 <property name="visible">True</property>
577 <property name="can_focus">False</property>
578 <property name="label" translatable="yes">COMPOSE</property>
579 <property name="angle">90</property>
580 </object>
581 <packing>
582 <property name="expand">False</property>
583 <property name="fill">True</property>
584 <property name="position">0</property>
585 </packing>
586 </child>
587 <child>
588 <object class="GtkBox">
589 <property name="visible">True</property>
590 <property name="can_focus">False</property>
591 <property name="spacing">6</property>
592 <child>
593 <object class="GtkBox">
594 <property name="visible">True</property>
595 <property name="can_focus">False</property>
596 <property name="orientation">vertical</property>
597 <property name="spacing">6</property>
598 <child>
599 <object class="GtkFrame">
600 <property name="visible">True</property>
601 <property name="can_focus">False</property>
602 <property name="label_xalign">0.5</property>
603 <property name="shadow_type">in</property>
604 <child>
605 <object class="GtkAlignment">
606 <property name="visible">True</property>
607 <property name="can_focus">False</property>
608 <property name="xalign">0</property>
609 <property name="bottom_padding">6</property>
610 <property name="left_padding">4</property>
611 <property name="right_padding">4</property>
612 <child>
613 <object class="GtkToolbar" id="toolbar_preview_a">
614 <property name="visible">True</property>
615 <property name="can_focus">False</property>
616 <property name="halign">center</property>
617 <property name="show_arrow">False</property>
618 </object>
619 </child>
620 </object>
621 </child>
622 <child type="label">
623 <object class="GtkLabel">
624 <property name="visible">True</property>
625 <property name="can_focus">False</property>
626 <property name="label" translatable="yes">CHANNEL A</property>
627 </object>
628 </child>
629 </object>
630 <packing>
631 <property name="expand">False</property>
632 <property name="fill">True</property>
633 <property name="position">0</property>
634 </packing>
635 </child>
636 <child>
637 <object class="GtkFrame" id="frame_preview_b">
638 <property name="name">preview_b</property>
639 <property name="visible">True</property>
640 <property name="can_focus">False</property>
641 <property name="label_xalign">0.5</property>
642 <property name="shadow_type">in</property>
643 <child>
644 <object class="GtkAlignment">
645 <property name="visible">True</property>
646 <property name="can_focus">False</property>
647 <property name="xalign">0</property>
648 <property name="bottom_padding">6</property>
649 <property name="left_padding">4</property>
650 <property name="right_padding">4</property>
651 <child>
652 <object class="GtkToolbar" id="toolbar_preview_b">
653 <property name="visible">True</property>
654 <property name="can_focus">False</property>
655 <property name="halign">center</property>
656 <property name="show_arrow">False</property>
657 </object>
658 </child>
659 </object>
660 </child>
661 <child type="label">
662 <object class="GtkLabel">
663 <property name="visible">True</property>
664 <property name="can_focus">False</property>
665 <property name="label" translatable="yes">CHANNEL B</property>
666 <property name="angle">2.2351741811588166e-10</property>
667 </object>
668 </child>
669 </object>
670 <packing>
671 <property name="expand">False</property>
672 <property name="fill">True</property>
673 <property name="position">1</property>
674 </packing>
675 </child>
676 </object>
677 <packing>
678 <property name="expand">False</property>
679 <property name="fill">True</property>
680 <property name="position">0</property>
681 </packing>
682 </child>
683 <child>
684 <object class="GtkFrame">
685 <property name="visible">True</property>
686 <property name="can_focus">False</property>
687 <property name="label_xalign">0.5</property>
688 <property name="shadow_type">in</property>
689 <child>
690 <object class="GtkAlignment">
691 <property name="visible">True</property>
692 <property name="can_focus">False</property>
693 <property name="xalign">0</property>
694 <property name="bottom_padding">6</property>
695 <property name="left_padding">4</property>
696 <property name="right_padding">4</property>
697 <child>
698 <object class="GtkToolbar" id="toolbar_preview_composite">
699 <property name="visible">True</property>
700 <property name="can_focus">False</property>
701 <property name="halign">center</property>
702 <property name="toolbar_style">text</property>
703 <property name="show_arrow">False</property>
704 <style>
705 <class name="secondary-toolbar"/>
706 </style>
707 </object>
708 </child>
709 </object>
710 </child>
711 <child type="label">
712 <object class="GtkLabel">
713 <property name="visible">True</property>
714 <property name="can_focus">False</property>
715 <property name="label" translatable="yes">COMPOSITE</property>
716 <property name="justify">center</property>
717 </object>
718 </child>
719 </object>
720 <packing>
721 <property name="expand">True</property>
722 <property name="fill">True</property>
723 <property name="position">1</property>
724 </packing>
725 </child>
726 <child>
727 <object class="GtkFrame" id="box_preview_modify">
728 <property name="visible">True</property>
729 <property name="can_focus">False</property>
730 <property name="label_xalign">0.5</property>
731 <property name="shadow_type">in</property>
732 <child>
733 <object class="GtkAlignment">
734 <property name="visible">True</property>
735 <property name="can_focus">False</property>
736 <property name="xalign">0</property>
737 <property name="bottom_padding">6</property>
738 <property name="left_padding">4</property>
739 <property name="right_padding">4</property>
740 <child>
741 <object class="GtkToolbar" id="toolbar_preview_mod">
742 <property name="visible">True</property>
743 <property name="can_focus">False</property>
744 <property name="halign">center</property>
745 <property name="show_arrow">False</property>
746 </object>
747 </child>
748 </object>
749 </child>
750 <child type="label">
751 <object class="GtkLabel">
752 <property name="visible">True</property>
753 <property name="can_focus">False</property>
754 <property name="label" translatable="yes">MODIFY</property>
755 <property name="justify">center</property>
756 </object>
757 </child>
758 </object>
759 <packing>
760 <property name="expand">True</property>
761 <property name="fill">True</property>
762 <property name="position">2</property>
763 </packing>
764 </child>
765 </object>
766 <packing>
767 <property name="expand">True</property>
768 <property name="fill">True</property>
769 <property name="position">1</property>
770 </packing>
771 </child>
772 </object>
773 <packing>
774 <property name="expand">False</property>
775 <property name="fill">True</property>
776 <property name="position">1</property>
777 </packing>
778 </child>
779 <child>
780 <object class="GtkBox" id="box_insert">
781 <property name="can_focus">False</property>
782 <property name="no_show_all">True</property>
783 <property name="spacing">6</property>
784 <child>
785 <object class="GtkLabel">
786 <property name="visible">True</property>
787 <property name="can_focus">False</property>
788 <property name="label" translatable="yes">OVERLAY</property>
789 <property name="angle">90</property>
790 </object>
791 <packing>
792 <property name="expand">False</property>
793 <property name="fill">True</property>
794 <property name="position">0</property>
795 </packing>
796 </child>
797 <child>
798 <object class="GtkBox">
799 <property name="visible">True</property>
800 <property name="can_focus">False</property>
801 <property name="orientation">vertical</property>
802 <child>
803 <object class="GtkFrame">
804 <property name="visible">True</property>
805 <property name="can_focus">False</property>
806 <property name="label_xalign">0.5</property>
807 <property name="shadow_type">in</property>
808 <child>
809 <object class="GtkAlignment">
810 <property name="visible">True</property>
811 <property name="can_focus">False</property>
812 <property name="bottom_padding">6</property>
813 <property name="left_padding">4</property>
814 <property name="right_padding">4</property>
815 <child>
816 <object class="GtkBox">
817 <property name="visible">True</property>
818 <property name="can_focus">False</property>
819 <property name="orientation">vertical</property>
820 <property name="spacing">2</property>
821 <child>
822 <object class="GtkLabel" id="overlay-description">
823 <property name="can_focus">False</property>
824 <property name="no_show_all">True</property>
825 <property name="margin_top">2</property>
826 <property name="margin_bottom">2</property>
827 <property name="wrap">True</property>
828 <attributes>
829 <attribute name="style" value="oblique"/>
830 </attributes>
831 </object>
832 <packing>
833 <property name="expand">False</property>
834 <property name="fill">True</property>
835 <property name="position">0</property>
836 </packing>
837 </child>
838 <child>
839 <object class="GtkBox">
840 <property name="visible">True</property>
841 <property name="can_focus">False</property>
842 <property name="margin_left">8</property>
843 <property name="margin_right">8</property>
844 <property name="spacing">6</property>
845 <child>
846 <object class="GtkCheckButton" id="insert-auto-off">
847 <property name="label" translatable="yes">AUTO-OFF</property>
848 <property name="name">auto-off</property>
849 <property name="visible">True</property>
850 <property name="can_focus">False</property>
851 <property name="receives_default">False</property>
852 <property name="tooltip_text" translatable="yes">automatically turn off insertion before every mix</property>
853 <property name="active">True</property>
854 <property name="draw_indicator">False</property>
855 <style>
856 <class name="option"/>
857 <class name="insertion"/>
858 <class name="overlay"/>
859 </style>
860 </object>
861 <packing>
862 <property name="expand">False</property>
863 <property name="fill">True</property>
864 <property name="position">1</property>
865 </packing>
866 </child>
867 <child>
868 <object class="GtkButton" id="update-inserts">
869 <property name="label" translatable="yes">UPDATE</property>
870 <property name="name">update</property>
871 <property name="visible">True</property>
872 <property name="can_focus">False</property>
873 <property name="receives_default">False</property>
874 <property name="tooltip_text" translatable="yes">Update current event</property>
875 </object>
876 <packing>
877 <property name="expand">False</property>
878 <property name="fill">True</property>
879 <property name="position">2</property>
880 </packing>
881 </child>
882 <child>
883 <object class="GtkComboBox" id="inserts">
884 <property name="name">inserts</property>
885 <property name="visible">True</property>
886 <property name="can_focus">False</property>
887 <property name="focus_on_click">False</property>
888 <property name="tooltip_text" translatable="yes">Select insert (overlay) from preconfigured ones </property>
889 <property name="model">insert-store</property>
890 <property name="column_span_column">0</property>
891 <property name="has_frame">False</property>
892 <child>
893 <object class="GtkCellRendererText" id="text">
894 <property name="single_paragraph_mode">True</property>
895 </object>
896 <attributes>
897 <attribute name="text">1</attribute>
898 </attributes>
899 </child>
900 </object>
901 <packing>
902 <property name="expand">True</property>
903 <property name="fill">True</property>
904 <property name="position">3</property>
905 </packing>
906 </child>
907 <child>
908 <object class="GtkCheckButton" id="insert">
909 <property name="label" translatable="yes">INSERT</property>
910 <property name="name">insert</property>
911 <property name="visible">True</property>
912 <property name="can_focus">False</property>
913 <property name="receives_default">False</property>
914 <property name="tooltip_text" translatable="yes">Show or hide current insertion</property>
915 <property name="draw_indicator">False</property>
916 </object>
917 <packing>
918 <property name="expand">False</property>
919 <property name="fill">True</property>
920 <property name="position">4</property>
921 </packing>
922 </child>
923 </object>
924 <packing>
925 <property name="expand">False</property>
926 <property name="fill">True</property>
927 <property name="position">1</property>
928 </packing>
929 </child>
930 </object>
931 </child>
932 </object>
933 </child>
934 <child type="label">
935 <object class="GtkLabel">
936 <property name="visible">True</property>
937 <property name="can_focus">False</property>
938 <property name="label" translatable="yes">INSERTION</property>
939 <property name="justify">center</property>
940 </object>
941 </child>
942 </object>
943 <packing>
944 <property name="expand">False</property>
945 <property name="fill">True</property>
946 <property name="position">0</property>
947 </packing>
948 </child>
949 </object>
950 <packing>
951 <property name="expand">True</property>
952 <property name="fill">True</property>
953 <property name="position">1</property>
954 </packing>
955 </child>
956 </object>
957 <packing>
958 <property name="expand">True</property>
959 <property name="fill">True</property>
960 <property name="position">2</property>
961 </packing>
962 </child>
963 </object>
964 <packing>
965 <property name="expand">False</property>
966 <property name="fill">True</property>
967 <property name="position">0</property>
968 </packing>
969 </child>
970 <child>
971 <object class="GtkFrame">
972 <property name="visible">True</property>
973 <property name="can_focus">False</property>
974 <property name="label_xalign">0.5</property>
975 <property name="shadow_type">in</property>
976 <child>
977 <object class="GtkAlignment">
978 <property name="visible">True</property>
979 <property name="can_focus">False</property>
980 <property name="xalign">0</property>
981 <property name="bottom_padding">6</property>
982 <property name="left_padding">4</property>
983 <property name="right_padding">4</property>
984 <child>
985 <object class="GtkToolbar" id="toolbar_mix">
986 <property name="visible">True</property>
987 <property name="can_focus">False</property>
988 <property name="halign">center</property>
989 <property name="orientation">vertical</property>
990 <property name="show_arrow">False</property>
991 </object>
992 </child>
993 </object>
994 </child>
995 <child type="label">
996 <object class="GtkLabel">
997 <property name="visible">True</property>
998 <property name="can_focus">False</property>
999 <property name="label" translatable="yes">MIX</property>
1000 </object>
1001 </child>
1002 </object>
1003 <packing>
1004 <property name="expand">False</property>
1005 <property name="fill">True</property>
1006 <property name="position">1</property>
1007 </packing>
1008 </child>
1009 </object>
1010 </child>
1011 <child type="tab">
1012 <object class="GtkLabel">
1013 <property name="visible">True</property>
1014 <property name="can_focus">False</property>
1015 <property name="label" translatable="yes">VIDEO</property>
1016 <property name="justify">fill</property>
1017 </object>
1018 <packing>
1019 <property name="tab_fill">False</property>
1020 </packing>
1021 </child>
1022 <child>
1023 <object class="GtkBox">
1024 <property name="visible">True</property>
1025 <property name="can_focus">False</property>
1026 <child>
1027 <object class="GtkLabel">
1028 <property name="visible">True</property>
1029 <property name="can_focus">False</property>
1030 </object>
1031 <packing>
1032 <property name="expand">True</property>
1033 <property name="fill">True</property>
1034 <property name="position">0</property>
1035 </packing>
1036 </child>
1037 <child>
1038 <object class="GtkBox" id="audio_box">
1039 <property name="visible">True</property>
1040 <property name="can_focus">False</property>
1041 <property name="margin_left">6</property>
1042 <property name="margin_right">6</property>
1043 <property name="margin_top">6</property>
1044 <property name="margin_bottom">6</property>
1045 <property name="spacing">6</property>
1046 <child>
1047 <placeholder/>
1048 </child>
1049 </object>
1050 <packing>
1051 <property name="expand">False</property>
1052 <property name="fill">True</property>
1053 <property name="position">1</property>
1054 </packing>
1055 </child>
1056 <child>
1057 <object class="GtkLabel">
1058 <property name="visible">True</property>
1059 <property name="can_focus">False</property>
1060 </object>
1061 <packing>
1062 <property name="expand">True</property>
1063 <property name="fill">True</property>
1064 <property name="position">2</property>
1065 </packing>
1066 </child>
1067 </object>
1068 <packing>
1069 <property name="position">1</property>
1070 </packing>
1071 </child>
1072 <child type="tab">
1073 <object class="GtkLabel">
1074 <property name="visible">True</property>
1075 <property name="can_focus">False</property>
1076 <property name="label" translatable="yes">AUDIO</property>
1077 <property name="justify">fill</property>
1078 </object>
1079 <packing>
1080 <property name="position">1</property>
1081 <property name="tab_fill">False</property>
1082 </packing>
1083 </child>
1084 <child>
1085 <placeholder/>
1086 </child>
1087 <child type="tab">
1088 <placeholder/>
1089 </child>
1090 <style>
1091 <class name="notebook"/>
1092 </style>
1093 </object>
1094 <packing>
1095 <property name="expand">False</property>
1096 <property name="fill">True</property>
1097 <property name="position">1</property>
1098 </packing>
1099 </child>
1100 </object>
1101 <packing>
1102 <property name="expand">False</property>
1103 <property name="fill">True</property>
1104 <property name="position">1</property>
1105 </packing>
1106 </child>
1107 <child>
1108 <object class="GtkLabel">
1109 <property name="visible">True</property>
1110 <property name="can_focus">False</property>
1111 <property name="hexpand">True</property>
1112 </object>
1113 <packing>
1114 <property name="expand">False</property>
1115 <property name="fill">True</property>
1116 <property name="position">2</property>
1117 </packing>
2131118 </child>
2141119 </object>
2151120 <packing>
2161121 <property name="expand">False</property>
2171122 <property name="fill">True</property>
218 <property name="position">0</property>
219 </packing>
220 </child>
221 <child>
222 <object class="GtkOverlay" id="video_overlay">
223 <property name="visible">True</property>
224 <property name="can_focus">False</property>
225 <property name="margin_left">5</property>
226 <property name="margin_right">5</property>
227 <property name="margin_top">5</property>
228 <property name="margin_bottom">5</property>
229 <child>
230 <object class="GtkDrawingArea" id="video_main">
231 <property name="width_request">800</property>
232 <property name="height_request">450</property>
233 <property name="visible">True</property>
234 <property name="can_focus">False</property>
235 <property name="double_buffered">False</property>
236 </object>
237 </child>
238 <child type="overlay">
239 <object class="GtkDrawingArea" id="video_overlay_drawingarea">
240 <property name="height_request">32</property>
241 <property name="app_paintable">True</property>
242 <property name="can_focus">False</property>
243 <property name="no_show_all">True</property>
244 <property name="valign">start</property>
245 <property name="vexpand">False</property>
246 </object>
247 </child>
248 </object>
249 <packing>
250 <property name="expand">True</property>
251 <property name="fill">True</property>
2521123 <property name="position">1</property>
2531124 </packing>
2541125 </child>
2551126 <child>
256 <object class="AudioLevelDisplay" id="audiolevel_main">
1127 <object class="GtkBox" id="box_toolbar_main">
2571128 <property name="visible">True</property>
2581129 <property name="can_focus">False</property>
259 <property name="margin_right">5</property>
260 <property name="margin_top">5</property>
261 <property name="margin_bottom">5</property>
1130 <child>
1131 <object class="GtkToolbar" id="toolbar_main">
1132 <property name="visible">True</property>
1133 <property name="can_focus">False</property>
1134 <property name="hexpand">True</property>
1135 <property name="toolbar_style">icons</property>
1136 <property name="show_arrow">False</property>
1137 <child>
1138 <object class="GtkSeparatorToolItem" id="s1">
1139 <property name="visible">True</property>
1140 <property name="can_focus">False</property>
1141 <property name="draw">False</property>
1142 </object>
1143 <packing>
1144 <property name="expand">True</property>
1145 <property name="homogeneous">True</property>
1146 </packing>
1147 </child>
1148 <child>
1149 <object class="GtkToolButton" id="voc-btn">
1150 <property name="visible">True</property>
1151 <property name="can_focus">False</property>
1152 <property name="label">voctomix2</property>
1153 <property name="icon_widget">img-voc</property>
1154 </object>
1155 <packing>
1156 <property name="expand">False</property>
1157 <property name="homogeneous">True</property>
1158 </packing>
1159 </child>
1160 <child>
1161 <object class="GtkToolButton" id="logo">
1162 <property name="visible">True</property>
1163 <property name="sensitive">False</property>
1164 <property name="can_focus">False</property>
1165 <property name="resize_mode">immediate</property>
1166 <property name="label" translatable="yes">Logo</property>
1167 <property name="use_underline">True</property>
1168 <property name="icon_widget">img-logo</property>
1169 </object>
1170 <packing>
1171 <property name="expand">False</property>
1172 <property name="homogeneous">True</property>
1173 </packing>
1174 </child>
1175 <child>
1176 <object class="GtkSeparatorToolItem" id="s6">
1177 <property name="visible">True</property>
1178 <property name="can_focus">False</property>
1179 <property name="draw">False</property>
1180 </object>
1181 <packing>
1182 <property name="expand">True</property>
1183 <property name="homogeneous">True</property>
1184 </packing>
1185 </child>
1186 <child>
1187 <object class="GtkToggleToolButton" id="ports_button">
1188 <property name="visible">True</property>
1189 <property name="can_focus">False</property>
1190 <property name="no_show_all">True</property>
1191 <property name="label">Ports</property>
1192 <property name="icon_widget">img-ports</property>
1193 </object>
1194 <packing>
1195 <property name="expand">False</property>
1196 <property name="homogeneous">True</property>
1197 </packing>
1198 </child>
1199 <child>
1200 <object class="GtkToggleToolButton" id="queue_button">
1201 <property name="visible">True</property>
1202 <property name="can_focus">False</property>
1203 <property name="no_show_all">True</property>
1204 <property name="label">Queues</property>
1205 <property name="icon_widget">img-queues</property>
1206 </object>
1207 <packing>
1208 <property name="expand">False</property>
1209 <property name="homogeneous">True</property>
1210 </packing>
1211 </child>
1212 <child>
1213 <object class="GtkSeparatorToolItem" id="s7">
1214 <property name="visible">True</property>
1215 <property name="can_focus">False</property>
1216 </object>
1217 <packing>
1218 <property name="expand">False</property>
1219 <property name="homogeneous">True</property>
1220 </packing>
1221 </child>
1222 <child>
1223 <object class="GtkToggleToolButton" id="mute_button">
1224 <property name="visible">True</property>
1225 <property name="can_focus">False</property>
1226 <property name="no_show_all">True</property>
1227 <property name="label">Mute Sound</property>
1228 <property name="icon_name">audio-volume-muted</property>
1229 </object>
1230 <packing>
1231 <property name="expand">False</property>
1232 <property name="homogeneous">True</property>
1233 </packing>
1234 </child>
1235 <child>
1236 <object class="GtkToggleToolButton" id="fullscreen">
1237 <property name="visible">True</property>
1238 <property name="can_focus">False</property>
1239 <property name="no_show_all">True</property>
1240 <property name="tooltip_text" translatable="yes">Enlarges main window to full screen
1241 Key: 'F11'</property>
1242 <property name="label">True</property>
1243 <property name="stock_id">gtk-fullscreen</property>
1244 </object>
1245 <packing>
1246 <property name="expand">False</property>
1247 <property name="homogeneous">True</property>
1248 </packing>
1249 </child>
1250 <child>
1251 <object class="GtkToolButton" id="close">
1252 <property name="visible">True</property>
1253 <property name="can_focus">False</property>
1254 <property name="no_show_all">True</property>
1255 <property name="tooltip_text" translatable="yes">Quits voctomix
1256 Key: 'Alt+F4'</property>
1257 <property name="use_underline">True</property>
1258 <property name="stock_id">gtk-close</property>
1259 </object>
1260 <packing>
1261 <property name="expand">False</property>
1262 <property name="homogeneous">True</property>
1263 </packing>
1264 </child>
1265 <style>
1266 <class name="primary-toolbar"/>
1267 </style>
1268 </object>
1269 <packing>
1270 <property name="expand">True</property>
1271 <property name="fill">True</property>
1272 <property name="position">0</property>
1273 </packing>
1274 </child>
2621275 </object>
2631276 <packing>
2641277 <property name="expand">False</property>
2681281 </child>
2691282 </object>
2701283 <packing>
271 <property name="expand">True</property>
1284 <property name="expand">False</property>
2721285 <property name="fill">True</property>
2731286 <property name="position">1</property>
2741287 </packing>
00 <?xml version="1.0" encoding="UTF-8"?>
1 <!-- Generated with glade 3.20.0 -->
1 <!-- Generated with glade 3.22.1 -->
22 <interface>
3 <object class="GtkAdjustment" id="audio_level_adjustment">
4 <property name="lower">-20</property>
5 <property name="upper">10</property>
6 <property name="step_increment">0.1</property>
7 </object>
8 <object class="GtkFrame" id="widget_preview">
3 <requires lib="gtk+" version="3.0"/>
4 <object class="GtkDrawingArea" id="video">
5 <property name="width_request">320</property>
6 <property name="height_request">180</property>
97 <property name="visible">True</property>
108 <property name="can_focus">False</property>
11 <property name="label_xalign">0</property>
12 <property name="shadow_type">none</property>
13 <child>
14 <object class="GtkAlignment" id="alignment">
15 <property name="visible">True</property>
16 <property name="can_focus">False</property>
17 <child>
18 <object class="GtkBox" id="box">
19 <property name="visible">True</property>
20 <property name="can_focus">False</property>
21 <property name="orientation">vertical</property>
22 <child>
23 <object class="GtkDrawingArea" id="video">
24 <property name="width_request">320</property>
25 <property name="height_request">180</property>
26 <property name="visible">True</property>
27 <property name="can_focus">False</property>
28 <property name="double_buffered">False</property>
29 <property name="margin_left">5</property>
30 <property name="margin_right">5</property>
31 <property name="margin_top">5</property>
32 <property name="margin_bottom">5</property>
33 </object>
34 <packing>
35 <property name="expand">False</property>
36 <property name="fill">True</property>
37 <property name="position">0</property>
38 </packing>
39 </child>
40 <child>
41 <object class="GtkBox" id="buttons">
42 <property name="visible">True</property>
43 <property name="can_focus">False</property>
44 <child>
45 <object class="GtkLabel" id="label">
46 <property name="visible">True</property>
47 <property name="can_focus">False</property>
48 <property name="margin_right">5</property>
49 <property name="label" translatable="yes">cam1</property>
50 </object>
51 <packing>
52 <property name="expand">True</property>
53 <property name="fill">True</property>
54 <property name="position">0</property>
55 </packing>
56 </child>
57 <child>
58 <object class="GtkRadioButton" id="btn_a">
59 <property name="label" translatable="yes">A</property>
60 <property name="width_request">40</property>
61 <property name="height_request">40</property>
62 <property name="visible">True</property>
63 <property name="can_focus">True</property>
64 <property name="receives_default">True</property>
65 <property name="margin_right">5</property>
66 <property name="draw_indicator">False</property>
67 <style>
68 <class name="btn_grp_a"/>
69 </style>
70 </object>
71 <packing>
72 <property name="expand">False</property>
73 <property name="fill">True</property>
74 <property name="position">1</property>
75 </packing>
76 </child>
77 <child>
78 <object class="GtkRadioButton" id="btn_b">
79 <property name="label" translatable="yes">B</property>
80 <property name="width_request">40</property>
81 <property name="height_request">40</property>
82 <property name="visible">True</property>
83 <property name="can_focus">True</property>
84 <property name="receives_default">True</property>
85 <property name="margin_right">5</property>
86 <property name="draw_indicator">False</property>
87 <style>
88 <class name="btn_grp_b"/>
89 </style>
90 </object>
91 <packing>
92 <property name="expand">False</property>
93 <property name="fill">True</property>
94 <property name="position">2</property>
95 </packing>
96 </child>
97 </object>
98 <packing>
99 <property name="expand">False</property>
100 <property name="fill">True</property>
101 <property name="position">1</property>
102 </packing>
103 </child>
104 <child>
105 <object class="GtkScale" id="audio_level">
106 <property name="visible">True</property>
107 <property name="can_focus">True</property>
108 <property name="margin_right">5</property>
109 <property name="adjustment">audio_level_adjustment</property>
110 <property name="restrict_to_fill_level">False</property>
111 <property name="round_digits">1</property>
112 <property name="digits">1</property>
113 <property name="value_pos">right</property>
114 <marks>
115 <mark value="-20">-&#x221e;</mark>
116 <mark value="0">0.0</mark>
117 </marks>
118 </object>
119 <packing>
120 <property name="expand">False</property>
121 <property name="fill">True</property>
122 <property name="position">2</property>
123 </packing>
124 </child>
125 </object>
126 </child>
127 </object>
128 </child>
129 <child type="label_item">
130 <placeholder/>
131 </child>
9 <property name="double_buffered">False</property>
13210 </object>
13311 </interface>
00 #!/usr/bin/env python3
11 import gi
2 # import GStreamer and GLib-Helper classes
3 gi.require_version('Gtk', '3.0')
4 gi.require_version('Gst', '1.0')
5 gi.require_version('GstVideo', '1.0')
6 gi.require_version('GstNet', '1.0')
7 from gi.repository import Gtk, Gdk, Gst, GstVideo
8
29 import signal
310 import logging
411 import sys
512 import os
613
7 # import GStreamer and GLib-Helper classes
8 gi.require_version('Gtk', '3.0')
9 gi.require_version('Gst', '1.0')
10 gi.require_version('GdkX11', '3.0')
11 gi.require_version('GstVideo', '1.0')
12 gi.require_version('GstNet', '1.0')
13 from gi.repository import Gtk, Gdk, Gst, GObject, GdkX11, GstVideo
14 sys.path.insert(0, '.')
15 from vocto.debug import gst_log_messages
1416
1517 # check min-version
1618 minGst = (1, 5)
2527 raise Exception('Python version', sys.version_info,
2628 'is too old, at least', minPy, 'is required')
2729
28 # init GObject & Co. before importing local classes
29 GObject.threads_init()
3030 Gdk.init([])
3131 Gtk.init([])
32
33 # select Awaita:Dark theme
34 settings = Gtk.Settings.get_default()
35 settings.set_property("gtk-theme-name", "Adwaita")
36 settings.set_property("gtk-application-prefer-dark-theme", True) # if you want use dark theme, set second arg to True
3237
3338
3439 # main class
3944 from lib.args import Args
4045 from lib.ui import Ui
4146
42 # Uf a UI-File was specified on the Command-Line, load it
43 if Args.ui_file:
44 self.log.info(
45 'loading ui-file from file specified on command-line: %s',
46 Args.ui_file
47 )
48 self.ui = Ui(Args.ui_file)
47 # Load UI file
48 path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ui/voctogui.ui')
49 self.log.info('Loading ui-file from file %s', path)
50 if os.path.isfile(path):
51 self.ui = Ui(path)
4952 else:
50 # Paths to look for the gst-switch UI-File
51 paths = [
52 os.path.join(os.path.dirname(os.path.realpath(__file__)),
53 'ui/voctogui.ui'),
54 '/usr/lib/voctogui/ui/voctogui.ui'
55 ]
56
57 # Look for a gst-switch UI-File and load it
58 self.ui = None
59 for path in paths:
60 self.log.debug('trying to load ui-file from file %s', path)
61
62 if os.path.isfile(path):
63 self.log.info('loading ui-file from file %s', path)
64 self.ui = Ui(path)
65 break
66
67 if self.ui is None:
68 raise Exception("Can't find any .ui-Files to use "
69 "(searched {})".format(', '.join(paths)))
53 raise Exception("Can't find any .ui-Files to use in {}".format(path))
7054
7155 #
7256 # search for a .css style sheet file and load it
7559 css_provider = Gtk.CssProvider()
7660 context = Gtk.StyleContext()
7761
78 css_paths = [
79 os.path.join(os.path.dirname(os.path.realpath(__file__)),
80 'ui/voctogui.css'),
81 '/usr/lib/voctogui/ui/voctogui.css'
82 ]
83
84 for path in css_paths:
85 self.log.debug('trying to load css-file from file %s', path)
86
87 if os.path.isfile(path):
88 self.log.info('loading css-file from file %s', path)
89 css_provider.load_from_path(path)
90 break
62 path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ui/voctogui.css')
63 self.log.info('Loading css-file from file %s', path)
64 if os.path.isfile(path):
65 css_provider.load_from_path(path)
9166 else:
92 raise Exception("Can't find any .css-Files to use "
93 "(searched {})".format(', '.join(css_paths)))
67 raise Exception("Can't find .css file '{}'".format(path))
9468
9569 context.add_provider_for_screen(
9670 Gdk.Screen.get_default(),
10175 self.ui.setup()
10276
10377 def run(self):
104 self.log.info('setting UI visible')
78 self.log.info('Setting UI visible')
10579 self.ui.show()
10680
10781 try:
108 self.log.info('running Gtk-MainLoop')
82 self.log.info('Running.')
10983 Gtk.main()
110 self.log.info('Gtk-MainLoop ended')
84 self.log.info('Connection lost. Exiting.')
11185 except KeyboardInterrupt:
11286 self.log.info('Terminated via Ctrl-C')
11387
11488 def quit(self):
115 self.log.info('quitting Gtk-MainLoop')
89 self.log.info('Quitting.')
11690 Gtk.main_quit()
11791
11892
130104 handler = LogHandler(docolor, Args.timestamp)
131105 logging.root.addHandler(handler)
132106
133 if Args.verbose >= 2:
134 level = logging.DEBUG
135 elif Args.verbose == 1:
136 level = logging.INFO
137 else:
138 level = logging.WARNING
107 levels = { 3 : logging.DEBUG, 2 : logging.INFO, 1 : logging.WARNING, 0 : logging.ERROR }
108 logging.root.setLevel(levels[Args.verbose])
139109
140 logging.root.setLevel(level)
110 gst_levels = { 3 : Gst.DebugLevel.DEBUG, 2 : Gst.DebugLevel.INFO, 1 : Gst.DebugLevel.WARNING, 0 : Gst.DebugLevel.ERROR }
111 gst_log_messages(gst_levels[Args.gstreamer_log])
141112
142113 # make killable by ctrl-c
143114 logging.debug('setting SIGINT handler')
154125
155126 # establish a synchronus connection to server
156127 import lib.connection as Connection
157 Connection.establish(
158 Args.host if Args.host else Config.get('server', 'host')
159 )
128 Connection.establish(Config.getHost())
160129
161130 # fetch config from server
162131 Config.fetchServerConfig()
165134 # The list-comparison is not complete
166135 # (one could use a local hostname or the local system ip),
167136 # but it's only here to warn that one might be making a mistake
168 use_previews = Config.getboolean('previews', 'enabled') \
169 and Config.getboolean('previews', 'use')
170 looks_like_localhost = Config.get('server', 'host') in ['::1',
171 '127.0.0.1',
172 'localhost']
173 if not use_previews and not looks_like_localhost:
137 localhosts = ['::1',
138 '127.0.0.1',
139 'localhost']
140 if not Config.getPreviewsEnabled() and Config.getHost() not in localhosts:
174141 logging.warning(
175142 'Connecting to `%s` (which looks like a remote host) '
176143 'might not work without enabeling the preview encoders '
177144 '(set `[previews] enabled=true` on the core) or it might saturate '
178145 'your ethernet link between the two machines.',
179 Config.get('server', 'host')
146 Config.getHost()
180147 )
181148
182149 import lib.connection as Connection