]> git.sesse.net Git - nageru-docs/blob - theme.rst
04146e8fce0003e701d66566419fb4ec869bf65d
[nageru-docs] / theme.rst
1 The theme
2 =========
3
4 In Nageru, most of the business logic around how your stream
5 ends up looking is governed by the **theme**, much like how a
6 theme works on a blog or a CMS. Most importantly, the theme
7 governs the look and feel through the different scenes and
8 transitions between them, such that an event gets a consistent
9 visual language even if the operators differ. Instead of requiring the user
10 to modify Nageru's C++ core, themes are written in
11 `Lua <https://www.lua.org/>`_, a lightweight scripting language
12 made for embedding.
13
14 Themes contain a lot of logic, and writing one can seem a bit
15 daunting at first. However, most events will be happy just tweaking
16 one of the themes included with Nageru, and the operator (if different
17 from the visual designer) will not need to worry at all.
18
19 Nageru ships with two themes, a default full-featured two-camera
20 setup with side-by-side for e.g. conferences, and a minimal one
21 that is easier for new users to understand.
22
23
24 Introduction to chains
25 ----------------------
26
27 Anything that's shown on the stream, or on one of the preview displays,
28 is created by a **Movit chain**. `Movit <https://movit.sesse.net/>`_
29 is a library for high-quality, high-performance video filters,
30 and Nageru's themes can use a simplified version of Movit's API where
31 most of the low-level details are abstracted away.
32
33 Every frame, the theme chooses a chain and a set of parameters to it,
34 based on what it thinks the picture should look like. Every chain
35 consists of a set of *inputs* (which can be either live video streams
36 or static pictures) and then a set of operators or *effects* to combine
37 or modify each other. Movit compiles these down to a set of shaders
38 that run in high speed on the GPU; the theme doesn't see a pixel,
39 and thus, Lua's performance (even though good for its language class,
40 especially if you use `LuaJIT <http://luajit.org/>`_) will not matter
41 much.
42
43
44 High- and low-quality chains
45 ----------------------------
46
47 The simplest possible chain takes only in an input and sends it on
48 to the display (the output of the last added node is always sent to
49 the screen, and in this case, that would be the input)::
50
51   local chain = EffectChain.new(16, 9)  -- Aspect ratio.
52   local input = chain:add_live_input(false, false)  -- No bounce override, no deinterlacing.
53   input:connect_signal(0)  -- First input card. Can be changed whenever you want.
54   chain:finalize(hq)
55
56 Note the “hq” parameter. Every chain needs to be able to run in two
57 situations: Both for the stream output (the ”live” pane) and for the preview
58 displays (the big “preview” pane and the :ref:`channels <channels>`). The details have
59 to do with Nageru internals (high-quality chains need to have an additional
60 Y'CbCr output), but the distinction is also useful for themes. In particular,
61 some operations, like scaling, can be done in various quality levels,
62 and for a low-resolution preview display, you don't need maximum quality.
63 Thus, in a preview chain (hq=false), you can safely take shortcuts.
64
65 The live chain is always processed in full resolution (typically 720p)
66 and then scaled down for the GUI. Preview chains are rendered in exactly
67 the resolution required, although of course, intermediate steps could be
68 bigger.
69
70
71 Setting parameters, and the get_chain entry point
72 -------------------------------------------------
73
74 Many effects support parameters that can vary per-frame. Imagine,
75 for instance, a theme where you want to supports two inputs and fading between
76 them. This means you will need a chain that produces two inputs and
77 produces a mix of them; Movit's *MixEffect* is exactly what you want here::
78
79   local chain = EffectChain.new(16, 9)
80
81   local input0 = chain:add_live_input(false, false)
82   input0:connect_signal(0)
83   local input1 = chain:add_live_input(false, false)
84   input1:connect_signal(1)
85
86   local mix_effect = chain:add_effect(MixEffect.new(), input0, input1)
87   chain:finalize(hq)
88
89 Every frame, Movit will call your **get_chain** function, which has
90 this signature:
91
92   function get_chain(num, t, width, height, signals)
93
94 “width” and “height” are what you'd expect (the output resolution).
95 t contains the current stream time in seconds. “num” contains 0
96 for the live view, 1 for the preview view, and 2, 3, 4, … for each
97 of the individual stream previews. “signals“ contains a bit of
98 information about each input signal, like its current resolution
99 or frame rate.
100
101 get_chain is in turn responsible for returning two values:
102
103   * The first return value is a Movit chain, as described in these
104     sections. For the live stream (num=0), you should return a high-quality
105     chain; for all others, you should return a low-quality chain.
106   * The second parameter is an *closure* that will be called just before
107     the chain is to be rendered. (The same chain could be used in
108     multiple OpenGL contexts at the same time, so you can't just set the values
109     immediately before returning. If you set them in the closure,
110     Nageru and Movit will deal with all the required threading for you.)
111
112 In the returned closure, you can set the parameters **strength_first**
113 and **strength_second**; for instance like this::
114
115   function get_chain(num, t, width, height, signals)
116     -- Assume num is 0 here; you will need to handle the other
117     -- cases, too.
118     prepare = function()
119       input0:connect_signal(0)
120       input1:connect_signal(1)
121
122       local fade_progress = 0.0
123       if t >= 1.0 and t >= 2.0:  -- Between 1 and 2 seconds; do the fade.
124         fade_progress = t - 1.0
125       elseif t >= 2.0:
126         fade_progress = 1.0
127       end
128
129       mix_effect:set_float("strength_first", 1.0 - fade_progress)
130       mix_effect:set_float("strength_second", fade_progress)
131     end
132     return chain, prepare
133   end
134
135 Note that in the case where fade_progress is 0.0 or 1.0 (you are just
136 showing one of the inputs), you are wasting GPU power by using the
137 fade chain; you should just return a simpler one-input chain instead.
138
139 The get_chain function is the backbone of every Nageru theme.
140 As we shall see, however, it may end up dealing with a fair bit
141 of complexity as the theme grows.
142
143
144 Chain precalculation
145 --------------------
146
147 Setting up and finalizing a chain is relatively fast, but it still
148 takes a measurable amount of CPU time, since it needs to create an OpenGL
149 shader and have it optimized by the driver; 50–100 ms is not uncommon.
150 Given that 60 fps means each frame is 16.7 ms, you cannot create new chains in
151 get_chain; every chain you could be using must be created at program start,
152 when your theme is initialized.
153
154 For any nontrivial theme, there are a lot of possible chains. Let's
155 return to the case of the MixEffect chain from the previous section.
156 Now let us assume that we could deal with signals that come in at
157 1080p instead of the native 720p. In this case, we will want a high-quality
158 scaler before mixing; *ResampleEffect* provides one::
159
160   local chain = EffectChain.new(16, 9)
161
162   local input0 = chain:add_live_input(false, false)
163   input0:connect_signal(0)
164   local input0_scaled = chain:add_effect(ResampleEffect.new())  -- Implicitly uses input0.
165   chain_or_input.resample_effect:set_int("width", 1280)  -- Would normally be set in the prepare function.
166   chain_or_input.resample_effect:set_int("height", 720)
167
168   local input1 = chain:add_live_input(false, false)
169   input1:connect_signal(1)
170
171   -- The rest is unchanged.
172
173 Clearly, there are four options here; both inputs could be unscaled,
174 input0 could be scaled but not input1, input1 could be scaled but not input0,
175 or both could be scaled. That means four chains.
176
177 Now remember that we need to create all your chains both in high-
178 and low-quality versions. In particular, this determines the “hq”
179 parameter to finalize(), but in our case, we would want to replace
180 ResampleEffect by *ResizeEffect* (a simpler scaling algorithm provided
181 directly by the GPU) for the low-quality versions. This makes for
182 eight chains.
183
184 Now also consider that we would want to deal with *interlaced*
185 inputs. (You can check if you get an interlaced input on the Nth
186 input by calling “signals:get_deinterlaced(n)” from get_chain.)
187 This further quadruples the number of chains you'd need to write,
188 and this isn't even including that you'd want the static chains.
189 It is obvious that this should not be done by hand. The default
190 included theme contains a handy Lua shortcut called
191 **make_cartesian_product** where you can declare all the dimensions
192 you would want to specialize your chain over, and have a callback
193 function called for each possible combination. Movit will make sure
194 each and every of those generated chains runs optimally on your GPU.
195
196
197 Transitions
198 -----------
199
200 As we have seen, the theme is king when it determines what to show
201 on screen. However, ultimately, it wants to delegate that power
202 to the operator. The abstraction presented from the theme to the user
203 is in the form of **transitions**. Every frame, Nageru calls the
204 following Lua entry point::
205
206   function get_transitions(t)
207
208 (t is again the stream time, but it is provided only for convenience;
209 not all themes would want to use it.) get_transitions must return an array of
210 (currently exactly) three strings, of which any can be blank. These three
211 strings are used as labels on one button each, and whenever the operator clicks
212 one of them, Nageru calls this function in the theme::
213
214   function transition_clicked(num, t)
215
216 where “num” is 0, 1 or 2, and t is again the theme time.
217
218 It is expected that the theme will use this and its internal state
219 to provide the abstraction (or perhaps illusion) of transitions to
220 the user. For instance, a theme will know that the live stream is
221 currently showing input 0 and the preview stream is showing input 1.
222 In this case, it can use two of the buttons to offer “Cut“ or “Fade”
223 transitions to the user. If the user clicks the cut button, the theme
224 can simply switch input and previews, which will take immediate
225 effect on the next frame. However, if the user clicks the fade button,
226 state will need to be set up so that next time get_chain() runs,
227 it will return the chain with the MixEffect, until it determines
228 the transition is over and changes back to showing only one input
229 (presumably the new one).
230
231
232 .. _channels:
233
234 Channels
235 --------
236
237 In addition to the live and preview outputs, a theme can declare
238 as many individual **channels** as it wants. These are shown at the
239 bottom of the screen, and are intended for the operator to see
240 what they can put up on the preview (in a sense, a preview of the
241 preview).
242
243 The number of channels is determined by calling this function
244 once at the start of the program::
245
246   function num_channels()
247
248 It should simply return the number of channels (0 is allowed,
249 but doesn't make a lot of sense). Live and preview comes in addition to this.
250
251 Each channel will have a label on it; Nageru asks the theme
252 by calling this function::
253
254   function channel_name(channel)
255
256 Here, channel is 2, 3, 4, etc.—0 is always called “Live” and
257 1 is always called “Preview”.
258
259 Each channel has its own chain, starting from number 2 for the first one
260 (since 0 is live and 1 is preview). The simplest form is simply a direct copy
261 of an input, and most themes will include one such channel for each input.
262 (Below, we will see that there are more types of channels, however.)
263 Since the mapping between the channel UI element and inputs is so typical,
264 Nageru allows the theme to simply declare that a channel corresponds to
265 a given signal, by asking it::
266
267   function channel_signal(channel)
268     if channel == 2 then
269       return 0
270     elseif channel == 3 then
271       return 1
272     else
273       return -1
274     end
275   end
276
277 Here, channels 2 and 3 (the two first ones) correspond directly to inputs
278 0 and 1, respectively. The others don't, and return -1. The effect on the
279 UI is that the user can right-click on the channel and configure the input
280 that way; in fact, this is currently the only way to configure them.
281
282 Furthermore, channels can have a color::
283
284   function channel_color(channel)
285  
286 The theme should return a CSS color (e.g. “#ff0000”, or “cyan”) for each
287 channel when asked; it can vary from frame to frame. A typical use is to mark
288 the currently playing input as red, or the preview as green.
289
290 And finally, there are two entry points related to white balance::
291
292   function supports_set_wb(channel)
293   function set_wb(channel, red, green, blue)
294
295 If the first function returns true (called once, at the start of the program),
296 the channel will get a “Set WB” button next to it, which will activate a color
297 picker. When the user picks a color (ostensibly with a gray point), the second
298 function will be called (with the RGB values in linear light—not sRGB!),
299 and the theme can then use it to adjust the white balance for that channel.
300 The typical way to to this is to have a *WhiteBalanceEffect* on each input
301 and set its “neutral_color” parameter using the “set_vec3” function.
302
303
304 More complicated channels: Scenes
305 ---------------------------------
306
307 Direct inputs are not the only kind of channels possible; again, any chain
308 can be output. The most common case is different kinds of **scenes**,
309 typically showing side-by-side or something similar. The typical UI presented
310 to the user in this case is that you create a channel that consists of the
311 finished setup; you use ResampleEffect (or ResizeEffect for low-quality chains),
312 *PaddingEffect* (to place the rectangles on the screen, one of them with a
313 transparent border) and then *OverlayEffect* (to get both on the screen at
314 the same time). Optionally, you can have a background image at the bottom,
315 and perhaps a logo at the top. This allows the operator to select a pre-made
316 scene, and then transition to and from it from a single camera view (or even
317 between different scenes) as needed.
318
319 Transitions involving scenes tend to be the most complicated parts of the theme
320 logic, but also make for the most distinct parts of your visual look.
321
322
323 .. _images:
324
325 Image inputs
326 ------------
327
328 In addition to video inputs, Nageru supports static **image inputs**.
329 These work pretty much the same way as live video inputs; however,
330 they need to be instantiated in a different way. Recall that live inputs
331 were created like this::
332
333   input = chain:add_live_input(false, deint)
334
335 Image inputs are instead created by instantiating *ImageInput* and
336 adding them manually to the chain::
337
338   input = chain:add_effect(ImageInput.new("bg.jpeg"))
339
340 Note that add_effect returns its input for convenience.
341
342 All image types supported by FFmpeg are supported; if you give in a video,
343 only the first frame is used. The file is checked once every second,
344 so if you update the file on-disk, it will be available in Nageru without
345 a restart. (If the file contains an error, the update will be ignored.)
346 This allows you to e.g. have simple message overlays that you can change
347 without restarting Nageru.
348
349
350 .. _menus:
351
352 Theme menus
353 -----------
354
355 Complicated themes, especially those dealing with :doc:`HTML inputs <html>`,
356 may have needs for user control that go beyond those of transition buttons.
357 (An obvious example may be “reload the HTML file”.) For this reason,
358 themes can also set simple *theme menus*, which are always visible
359 no matter what inputs are chosen.
360
361 If a theme chooses to set a theme menu, it will be available on the
362 main menu bar under “Theme”; if not, it will be hidden. You can set
363 the menu at startup or at any other point, using a simple series of
364 labels and function references::
365
366   function modify_aspect()
367     -- Your code goes here.
368   end
369
370   function reload_html()
371     html_input:reload()
372   end
373
374   ThemeMenu.set(
375     { "Change &aspect", modify_aspect },
376     { "&Reload overlay", reload_html }
377   )
378
379 When the user chooses a menu entry, the given Lua function will
380 automatically be called. There are no arguments nor return values.
381
382 There currently is no support for checkboxes, submenus, input boxes
383 or the likes. However, do note that since the theme is written in unrestricted
384 Lua, so you can use e.g. `lua-http <https://github.com/daurnimator/lua-http>`_
385 to listen for external connections and accept more complicated inputs
386 from those.