]> git.sesse.net Git - nageru-docs/blob - theme.rst
We are up-to-date with 1.9.2.
[nageru-docs] / theme.rst
1 The theme
2 =========
3
4 **NOTE**: Nageru 1.9.0 made significant improvements to themes
5 and how scenes work. If you use an older version, you may want
6 to look at `the 1.8.6 documentation <https://nageru.sesse.net/doc-1.8.6/>`_;
7 themes written for older versions still work without modification in
8 1.9.0, but are not documented here, and you are advised to change
9 to use the new interfaces, as they are equally powerful and much simpler
10 to work with.
11
12 In Nageru, most of the business logic around how your stream
13 ends up looking is governed by the **theme**, much like how a
14 theme works on a blog or a CMS. Most importantly, the theme
15 governs the look and feel through the different scenes and
16 transitions between them, such that an event gets a consistent
17 visual language even if the operators differ. Instead of requiring the user
18 to modify Nageru's C++ core, themes are written in
19 `Lua <https://www.lua.org/>`_, a lightweight scripting language
20 made for embedding.
21
22 Themes contain a lot of logic, and writing one can seem a bit
23 daunting at first. However, most events will be happy just tweaking
24 one of the themes included with Nageru, and the operator (if different
25 from the visual designer) will not need to worry at all.
26
27 Nageru ships with two themes, a default full-featured two-camera
28 setup with side-by-side for e.g. conferences, and a minimal one
29 that is easier for new users to understand.
30
31
32 Introduction to scenes
33 ----------------------
34
35 Anything that's shown on the stream, or on one of the preview displays,
36 is created by a **Movit chain**, instantiated by a Nageru **scene**.
37 `Movit <https://movit.sesse.net/>`_
38 is a library for high-quality, high-performance video filters,
39 and Nageru's themes can use a simplified version of Movit's API where
40 most of the low-level details are abstracted away.
41
42 Every frame, the theme chooses a **scene** and a set of parameters to it,
43 based on what it thinks the picture should look like. Every scene
44 consists of a set of *inputs* (which can be either live video streams
45 or static pictures) and then a set of operators or *effects* to combine
46 or modify each other. Movit compiles these down to a set of shaders
47 that run in high speed on the GPU; the theme doesn't see a pixel,
48 and thus, Lua's performance (even though good for its language class,
49 especially if you use `LuaJIT <http://luajit.org/>`_) will not matter
50 much.
51
52
53 Basic scenes
54 ------------
55
56 The simplest possible scene takes only in an input and sends it on
57 to the display (the output of the last added node is always sent to
58 the screen, and in this case, that would be the input)::
59
60   local scene = Scene.new(16, 9)  -- Aspect ratio.
61   local input = scene:add_input()
62   input:display(0)  -- First input card. Can be changed whenever you want.
63   scene:finalize()
64
65 The live scene is always processed in full resolution (typically 720p)
66 and then scaled down for the GUI. Preview scenes 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_scene 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 scene that produces two inputs and
77 produces a mix of them; Movit's *MixEffect* is exactly what you want here::
78
79   local scene = EffectChain.new(16, 9)
80
81   local input0 = scene:add_input()
82   input0:display(0)
83   local input1 = scene:add_input()
84   input1:display(1)
85
86   -- Note that add_effect returns its input for convenience.
87   local mix_effect = scene:add_effect(MixEffect.new(), input0, input1)
88   scene:finalize()
89
90 Every frame, Movit will call your **get_scene** function, which has
91 this signature:
92
93   function get_scene(num, t, width, height, signals)
94
95 “width” and “height” are what you'd expect (the output resolution).
96 t contains the current stream time in seconds. “num” contains 0
97 for the live view, 1 for the preview view, and 2, 3, 4, … for each
98 of the individual stream previews. “signals” contains a bit of
99 information about each input signal (see :ref:`signal-info`).
100
101 get_scene in return should return a scene. However, before you do that,
102 you can set the parameters **strength_first** and **strength_second**
103 on it; for instance like this::
104
105   function get_scene(num, t, width, height, signals)
106     -- Assume num is 0 here; you will need to handle the other
107     -- cases, too.
108     local fade_progress = 0.0
109     if t >= 1.0 and t >= 2.0:  -- Between 1 and 2 seconds; do the fade.
110       fade_progress = t - 1.0
111     elseif t >= 2.0:
112       fade_progress = 1.0
113     end
114
115     mix_effect:set_float("strength_first", 1.0 - fade_progress)
116     mix_effect:set_float("strength_second", fade_progress)
117     return scene
118   end
119
120 Note that in the case where fade_progress is 0.0 or 1.0 (you are just
121 showing one of the inputs), you are wasting GPU power by using the
122 fade scene; you should just return a simpler one-input scene instead.
123
124 The get_scene function is the backbone of every Nageru theme.
125 As we shall see, however, it may end up dealing with a fair bit
126 of complexity as the theme grows.
127
128
129 Scene variants and effect alternatives
130 --------------------------------------
131
132 Setting up and finalizing a scene is relatively fast, but it still
133 takes a measurable amount of CPU time, since it needs to create an OpenGL
134 shader and have it optimized by the driver; 50–100 ms is not uncommon.
135 Given that 60 fps means each frame is 16.7 ms, you cannot create new scenes in
136 get_scene; every scene you could be using must be created at program start,
137 when your theme is initialized.
138
139 For any nontrivial theme, there are a lot of possible scenes. Let's
140 return to the case of the MixEffect scene from the previous section.
141 Now let us assume that we could deal with signals that come in at
142 1080p instead of the native 720p. In this case, we will want a high-quality
143 scaler before mixing; *ResampleEffect* provides one::
144
145   local scene = EffectChain.new(16, 9)
146
147   local input0 = scene:add_input()
148   input0:display(0)
149   local input0_scaled = scene:add_optional_effect(ResampleEffect.new())  -- Implicitly uses input0.
150   input0_scaled:set_int("width", 1280)  -- Could also be set in get_scene().
151   input0_scaled:set_int("height", 720)
152   input0_scaled:enable()  -- Enable or disable as needed.
153
154   local input1 = scene:add_input()
155   input1:display(1)
156   local input1_scaled = ... -- Similarly here and the rest.
157
158   input1_scaled:enable_if(some_variable)  -- Convenience form for enable() or disable() depending on some_variable.
159
160   -- The rest is unchanged.
161
162 Clearly, there are four options here; both inputs could be unscaled,
163 input0 could be scaled but not input1, input1 could be scaled but not input0,
164 or both could be scaled. That means four scenes. However, you don't need to
165 care about this; behind the scenes (no pun intended), Nageru will make all
166 four versions for you and choose the right one as you call enable() or
167 disable() on each effect.
168
169 Beyond simple on/off switches, an effect can have many *alternatives*,
170 by giving in an array of effects. For instance, it is usually pointless to use
171 the high-quality resampling provided by ResampleEffect for the on-screen
172 outputs; we can use *ResizeEffect* (a simpler scaling algorithm provided
173 directly by the GPU) that instead. The scaling is set up like this::
174
175   local input0 = scene:add_input()
176   input0:display(0)
177   local input0_scaled = scene:add_effect({ResampleEffect.new(), ResizeEffect.new()})  -- Implicitly uses input0.
178   input0_scaled:set_int("width", 1280)  -- Just like before.
179   input0_scaled:set_int("height", 720)
180
181   -- Pick one in get_scene() like this:
182   input0_scaled:choose(ResizeEffect)
183
184   -- Or by numerical index:
185   input0_scaled:choose(1)  -- Chooses ResizeEffect
186
187 Actually, add_optional_effect() is just a wrapper around add_effect() with
188 IdentityEffect as the other alternative, and disable() is a convenience version of
189 choose(IdentityEffect).
190
191 All alternatives must
192 have the same amount of inputs, with an exception for IdentityEffect, which can
193 coexist with an effect requiring any amount of inputs (if selected, the IdentityEffect
194 just passes its first input unchanged). Similarly, if you set a parameter with
195 set_int() or similar, it must be valid for all alternatives (again excepting
196 IdentityEffect); if there is one that can only be used on a certain alternative,
197 you must set it directly on the effect::
198
199   local resample_effect = ResampleEffect.new()
200   resample_effect:set_float("zoom_x", 1.0001)  -- Not valid for ResizeEffect.
201    
202   local input0_scaled = scene:add_effect({resample_effect, ResizeEffect.new()})
203   input0_scaled:set_int("width", 1280)  -- Set on both alternatives.
204   input0_scaled:set_int("height", 720)
205
206   -- This is also possible, as choose() returns the chosen effect:
207   input0_scaled:choose(ResampleEffect):set_float("zoom_y", 1.0001)
208
209 Actually, more versions are created than you'd immediately expect.
210 In particular, the output format for the live output and all previews are
211 different (Y'CbCr versus RGBA), which is also handled transparently for you.
212 Also, the inputs could be interlaced, or they could be images, or videos (see
213 :ref:`images` and :doc:`video`), creating many more options. Again, you
214 generally don't need to care about this; Movit will make sure each and every of
215 those generated scenes runs optimally on your GPU. However, if the
216 combinatorial explosion increases startup time beyond what you are comfortable
217 with, see :ref:`locking`.
218
219
220 Transitions
221 -----------
222
223 As we have seen, the theme is king when it determines what to show
224 on screen. However, ultimately, it wants to delegate that power
225 to the operator. The abstraction presented from the theme to the user
226 is in the form of **transitions**. Every frame, Nageru calls the
227 following Lua entry point::
228
229   function get_transitions(t)
230
231 (t is again the stream time, but it is provided only for convenience;
232 not all themes would want to use it.) get_transitions must return an array of
233 (currently exactly) three strings, of which any can be blank. These three
234 strings are used as labels on one button each, and whenever the operator clicks
235 one of them, Nageru calls this function in the theme::
236
237   function transition_clicked(num, t)
238
239 where “num” is 0, 1 or 2, and t is again the theme time.
240
241 It is expected that the theme will use this and its internal state
242 to provide the abstraction (or perhaps illusion) of transitions to
243 the user. For instance, a theme will know that the live stream is
244 currently showing input 0 and the preview stream is showing input 1.
245 In this case, it can use two of the buttons to offer “Cut“ or “Fade”
246 transitions to the user. If the user clicks the cut button, the theme
247 can simply switch input and previews, which will take immediate
248 effect on the next frame. However, if the user clicks the fade button,
249 state will need to be set up so that next time get_scene() runs,
250 it will return the scene with the MixEffect, until it determines
251 the transition is over and changes back to showing only one input
252 (presumably the new one).
253
254
255 .. _channels:
256
257 Channels
258 --------
259
260 In addition to the live and preview outputs, a theme can declare
261 as many individual **channels** as it wants. These are shown at the
262 bottom of the screen, and are intended for the operator to see
263 what they can put up on the preview (in a sense, a preview of the
264 preview).
265
266 The number of channels is determined by calling this function
267 once at the start of the program::
268
269   Nageru.set_num_channels(2)
270
271 0 is allowed, but doesn't make a lot of sense. Live and preview comes in
272 addition to this.
273
274 Each channel will have a label on it; you set it by calling::
275
276   Nageru.set_channel_name(2, "Side-by-side")
277
278 Here, channel is 2, 3, 4, etc.—by default, 0 is called “Live” and
279 1 is called “Preview”, and you probably don't need to change this.
280
281 Each channel has its own scene, starting from number 2 for the first one
282 (since 0 is live and 1 is preview). The simplest form is simply a direct copy
283 of an input, and most themes will include one such channel for each input.
284 (Below, we will see that there are more types of channels, however.)
285 Since the mapping between the channel UI element and inputs is so typical,
286 Nageru allows the theme to simply declare that a channel corresponds to
287 a given signal::
288
289   Nageru.set_channel_signal(2, 0)
290   Nageru.set_channel_signal(3, 1)
291
292 Here, channels 2 and 3 (the two first ones) correspond directly to inputs
293 0 and 1, respectively. The others don't, and return -1. The effect on the
294 UI is that the user can right-click on the channel and configure the input
295 that way; in fact, this is currently the only way to configure them.
296
297 Furthermore, channels can have a color, which is governed by Nageru calling
298 a function your theme::
299
300   function channel_color(channel)
301  
302 The theme should return a CSS color (e.g. “#ff0000”, or “cyan”) for each
303 channel when asked; it can vary from frame to frame. A typical use is to mark
304 the currently playing input as red, or the preview as green.
305
306
307 .. _white-balance:
308
309 White balance
310 .............
311
312 Finally, there are two entry points related to white balance. The first one
313 is::
314
315   Nageru.set_supports_wb(2, true)
316
317 If the first function is called with a true value (at the start of the theme),
318 the channel will get a “Set WB” button next to it, which will activate a color
319 picker, to select the gray point. To actually *apply* this white balance change,
320 you have two options. If you're using Nageru 1.9.2 or newer, it's as simple
321 as adding one element to the scene::
322
323   scene:add_white_balance()
324
325 The white balance effect will automatically figure out which input it is
326 connected to, and fetch its gray point if needed. (If it is connected to
327 e.g. a mix of several inputs, such as a camera and an overlay, you will need to
328 give the input to fetch white balance from as as a parameter.)
329
330 If, on the other hand, you are using Nageru 1.9.1 or older (or just wish
331 for more manual control), there's an entry point you will need to implement::
332
333   function set_wb(channel, red, green, blue)
334
335 When the user picks a gray point, this function
336 function will be called (with the RGB values in linear light—not sRGB!),
337 and the theme can then use it to adjust the white balance for that channel.
338 The typical way to to this is to have a *WhiteBalanceEffect* on each input
339 and set its “neutral_color” parameter using the “set_vec3” function.
340
341
342 More complicated channels: Composites
343 -------------------------------------
344
345 Direct inputs are not the only kind of channels possible; again, any scene
346 can be output. The most common case is different kinds of **composites**,
347 typically showing side-by-side or something similar. The typical UI presented
348 to the user in this case is that you create a channel that consists of the
349 finished setup; you use ResampleEffect (or ResizeEffect for preview scenes),
350 *PaddingEffect* (to place the rectangles on the screen, one of them with a
351 transparent border) and then *OverlayEffect* (to get both on the screen at
352 the same time). Optionally, you can have a background image at the bottom,
353 and perhaps a logo at the top. This allows the operator to select a pre-made
354 composits, and then transition to and from it from a single camera view (or even
355 between different composites) as needed.
356
357 Transitions involving composites tend to be the most complicated parts of the theme
358 logic, but also make for the most distinct parts of your visual look.
359
360
361 .. _images:
362
363 Image inputs
364 ------------
365
366 In addition to video inputs, Nageru supports static **image inputs**.
367 These work pretty much the same way as live video inputs. Recall that
368 you chose what input to display like this::
369
370   input:display(0)
371
372 Image inputs are instead created by instantiating *ImageInput* and
373 displaying that::
374
375   bg = ImageInput.new("bg.jpeg")  -- Once, at the start of the program.
376   input:display(bg)  -- In get_scene().
377
378 All image types supported by FFmpeg are supported; if you give in a video,
379 only the first frame is used. The file is checked once every second,
380 so if you update the file on-disk, it will be available in Nageru without
381 a restart. (If the file contains an error, the update will be ignored.)
382 This allows you to e.g. have simple message overlays that you can change
383 without restarting Nageru.
384
385
386 .. _locking:
387
388 Locking alternatives
389 --------------------
390
391 In some cases, Nageru may be building in alternatives to a scene that you
392 don't really need, resulting in combinatorial explosion. (If the number of
393 instances is getting high, you will get a warning when finalizing the scene.)
394 For instance, in some cases, you know that a given transition scene will never
395 be used for previews, just live. In this case, you can replace the call to
396 scene:finalize() with::
397
398   scene:finalize(false)
399
400 In this case, you guarantee that the scene will never be returned when
401 get_scene() is called with the number 0. (Similarly, you can use true
402 to *only* use it for the live channel.)
403
404 Similarly, inputs can hold four different input types, but in some scenes,
405 you may always use them with a specific one, e.g. an image “bg_img”. In this case,
406 you may add the input with a specific type right away::
407
408   scene:add_input(bg_img)
409
410 Similarly, for a live input, you can do::
411
412   scene:add_input(0)
413
414 You can still use scene:display() to change the input, but it needs to be of
415 the same *type* as the one you gave to add_input().
416
417 Finally, you can specify that some effects only make sense together, reducing
418 the number of possibilities further. For instance, you may have an optional
419 crop effect followed by a resample, where the resample is only enabled if the
420 crop is. If so, you can do this::
421
422    resample_effect:always_disable_if_disabled(crop_effect)
423
424 Also, since Nageru 1.9.1, you can disable an optional effect if a given other
425 effect is *enabled*::
426
427    overlay1_effect:promise_to_disable_if_enabled(overlay2_effect)
428    overlay2_effect:promise_to_disable_if_enabled(overlay1_effect)
429
430 Note that the latter is a promise from the user, not automatic disabling; since
431 it is mostly useful for mutual exclusions, Nageru wouldn't know which of the
432 two to disable. (If you violate the promise, you will get an error message at
433 runtime.) It can still be useful for reducing the number of alternatives, though.
434
435 For more advanced exclusions, you may choose to split up the scenes into several
436 distinct ones that you manage yourself; indeed, before Nageru 1.9.0, that was
437 the only option. At some point, however, you may choose to simply accept the
438 added startup time and a bit of extra RAM cost; ease of use and flexibility often
439 trumps such concerns.
440
441
442 .. _menus:
443
444 Theme menus
445 -----------
446
447 Complicated themes, especially those dealing with :doc:`HTML inputs <html>`,
448 may have needs for user control that go beyond those of transition buttons.
449 (An obvious example may be “reload the HTML file”.) For this reason,
450 themes can also set simple *theme menus*, which are always visible
451 no matter what inputs are chosen.
452
453 If a theme chooses to set a theme menu, it will be available on the
454 main menu bar under “Theme”; if not, it will be hidden. You can set
455 the menu at startup or at any other point, using a simple series of
456 labels and function references::
457
458   function modify_aspect()
459     -- Your code goes here.
460   end
461
462   function reload_html()
463     html_input:reload()
464   end
465
466   ThemeMenu.set(
467     { "Change &aspect", modify_aspect },
468     { "&Reload overlay", reload_html }
469   )
470
471 When the user chooses a menu entry, the given Lua function will
472 automatically be called. There are no arguments nor return values.
473
474 Menus can contain submenus, by giving an array instead of a function::
475
476   ThemeMenu.set(
477     { "Overlay", {
478        { "Version A", select_overlay_a },
479        { "Version B", select_overlay_b }
480     },
481     { "&Reload overlay", reload_html }
482   )
483
484 They can also be checkable, or have checkboxes, by adding a third
485 array element containing flags for that::
486
487   ThemeMenu.set(
488     { "Enable overlay",  enable_overlay,  Nageru.CHECKED },    -- Currently checked.
489     { "Enable crashing", make_unstable,   Nageru.CHECKABLE }   -- Can be checked, but isn't currently.
490   )
491
492 When such an option is selected, you probably want to rebuild the menu to
493 reflect the new state.
494
495 There currently is no support for input boxes, sliders,
496 or the likes. However, do note that since the theme is written in unrestricted
497 Lua, so you can use e.g. `lua-http <https://github.com/daurnimator/lua-http>`_
498 to listen for external connections and accept more complicated inputs
499 from those.
500
501
502 .. _signal-info:
503
504 Signal information queries
505 --------------------------
506
507 As previously mentioned, get_scene() takes in a “signals” parameter
508 that you can query for information about each signal (numbered from 0;
509 live and preview are channels, not signals), like its current resolution
510 or frame rate:
511
512   * get_frame_width(signal), get_frame_height(signal): Width and height of the last frame.
513   * get_width(signal), get_height(signal): Width and height of the last *field*
514     (the field height is half of the frame height for an interlaced signal).
515   * get_interlaced(signal): Whether the last frame was interlaced.
516   * get_has_signal(signal): Whether there is a valid input signal.
517   * get_is_connected(signal): Whether there is even a card connected
518     to this signal (USB cards can be swapped in or out); if not,
519     you will get a stream of single-colored frames.
520   * get_frame_rate_nom(signal), get_frame_rate_den(signal): The frame rate
521     of the last frame, as a rational (e.g. 60/1, or 60000/1001 for 59.94).
522   * get_last_subtitle(signal): See :ref:`subtitle-ingest`.
523   * get_human_readable_resolution(signal): The resolution and frame rate in
524     human-readable form (e.g. “1080i59.94”), suitable for e.g. stream titles.
525     Note that Nageru does not follow the EBU recommendation of using
526     frame rate even for interlaced signals (e.g. “1080i25” instead of “1080i50”),
527     since it is little-used and confusing to most users.
528
529 You can use this either for display purposes, or for choosing the right
530 effect alternatives. In particular, you may want to disable scaling if
531 the frame is already of the correct resolution.
532
533
534 Audio control
535 -------------
536
537 Before you attempt to control audio from the theme, be sure to have read
538 the documentation about :doc:`audio`.
539
540 Since Nageru 1.9.2, the theme has a certain amount of control over the audio
541 mix, assuming that you are in multichannel mode. This is useful in particular
542 to be able to set defaults, if e.g. one channel should always be muted at
543 startup, or to switch in/out certain channels depending on whether they are
544 visible or not.
545
546 In particular, these operations are available::
547
548   # Returns number of buses in the mapping.
549   local num_buses = Nageru.get_num_audio_buses()
550
551   # Gets the name from the mapping. All indexes start at zero,
552   # so valid indexes are 0..(num_buses-1), inclusive.
553   local name = Nageru.get_audio_bus_name(N)
554
555   # 0.0 is zero amplification, as in the UI. Valid range is
556   # -inf to +6.0, inclusive.
557   local level = Nageru.get_audio_bus_fader_level_db(N)
558   set_audio_bus_fader_level_db(N, level)
559
560   # Similar as the above, but valid range is -15.0..+15.0 (dB).
561   # Valid bands are Nageru.EQ_BAND_{BASS, MID, TREBLE}.
562   local eq_level = Nageru.get_audio_bus_eq_level_db(N, Nageru.EQ_BAND_BASS)
563   Nageru.set_audio_bus_eq_level_db(N, Nageru.EQ_BAND_BASS, level)
564
565   # A boolean. Does not affect the bus levels set/returned above.
566   local muted = Nageru_get_audio_bus_mute(N)
567   Nageru_set_audio_bus_mute(N, false)
568
569 Note that any audio operation is inherently unsynchronized with the UI,
570 so if the user reduces the number of audio buses while
571 the theme tries to access one that is going away, you may get unpredictable
572 behavior, up to and including crashes. Thus, you will need to be careful
573 with such operations.
574
575 Also, you cannot do anything with audio before the first *get_scene()* call,
576 since the audio mixer is initialized only after the theme has been loaded and
577 initialized. Thus, for things that should be done only the first frame, the
578 recommended method is to put code into get_scene() and have a guard variable
579 that makes sure it is only run
580 once, ever.
581
582
583 Overriding the status line
584 --------------------------
585
586 Some users may wish to override the status line, e.g. with recording time.
587 If so, it is possible (since Nageru 1.9.1) to declare a function **format_status_line**::
588
589   function format_status_line(disk_space_text, file_length_seconds)
590     if file_length_seconds > 86400.0 then
591       return "Time to make a new segment"
592     else
593       return "Disk space left: " .. disk_space_text
594     end
595   end
596
597 As demonstrated, it is given the disk space text (that would normally
598 be there), and the length of the current recording file in seconds.
599 HTML is supported.