]> git.sesse.net Git - nageru-docs/blobdiff - theme.rst
Document the return value of choose() and how it is useful.
[nageru-docs] / theme.rst
index 39424bdb8dfd1fabb0acd8a810d2a8fc5eb2d99f..7fbe735b1ce6f081947603c1f4c0386b217f1982 100644 (file)
--- a/theme.rst
+++ b/theme.rst
@@ -83,6 +83,7 @@ produces a mix of them; Movit's *MixEffect* is exactly what you want here::
   local input1 = scene:add_input()
   input1:display(1)
 
+  -- Note that add_effect returns its input for convenience.
   local mix_effect = scene:add_effect(MixEffect.new(), input0, input1)
   scene:finalize()
 
@@ -94,9 +95,8 @@ this signature:
 “width” and “height” are what you'd expect (the output resolution).
 t contains the current stream time in seconds. “num” contains 0
 for the live view, 1 for the preview view, and 2, 3, 4, … for each
-of the individual stream previews. “signals“ contains a bit of
-information about each input signal, like its current resolution
-or frame rate.
+of the individual stream previews. “signals” contains a bit of
+information about each input signal (see :ref:`signal-info`).
 
 get_scene in return should return a scene. However, before you do that,
 you can set the parameters **strength_first** and **strength_second**
@@ -147,14 +147,16 @@ scaler before mixing; *ResampleEffect* provides one::
   local input0 = scene:add_input()
   input0:display(0)
   local input0_scaled = scene:add_optional_effect(ResampleEffect.new())  -- Implicitly uses input0.
-  scene_or_input.resample_effect:set_int("width", 1280)  -- Could also be set in get_scene().
-  scene_or_input.resample_effect:set_int("height", 720)
+  input0_scaled:set_int("width", 1280)  -- Could also be set in get_scene().
+  input0_scaled:set_int("height", 720)
   input0_scaled:enable()  -- Enable or disable as needed.
 
   local input1 = scene:add_input()
   input1:display(1)
   local input1_scaled = ... -- Similarly here and the rest.
 
+  input1_scaled:enable_if(some_variable)  -- Convenience form for enable() or disable() depending on some_variable.
+
   -- The rest is unchanged.
 
 Clearly, there are four options here; both inputs could be unscaled,
@@ -173,8 +175,8 @@ directly by the GPU) that instead. The scaling is set up like this::
   local input0 = scene:add_input()
   input0:display(0)
   local input0_scaled = scene:add_effect({ResampleEffect.new(), ResizeEffect.new()})  -- Implicitly uses input0.
-  scene_or_input.resample_effect:set_int("width", 1280)  -- Just like before.
-  scene_or_input.resample_effect:set_int("height", 720)
+  input0_scaled:set_int("width", 1280)  -- Just like before.
+  input0_scaled:set_int("height", 720)
 
   -- Pick one in get_scene() like this:
   input0_scaled:choose(ResizeEffect)
@@ -182,12 +184,28 @@ directly by the GPU) that instead. The scaling is set up like this::
   -- Or by numerical index:
   input0_scaled:choose(1)  -- Chooses ResizeEffect
 
-Note that add_effect returns its input for convenience.
-
 Actually, add_optional_effect() is just a wrapper around add_effect() with
 IdentityEffect as the other alternative, and disable() is a convenience version of
 choose(IdentityEffect).
 
+All alternatives must
+have the same amount of inputs, with an exception for IdentityEffect, which can
+coexist with an effect requiring any amount of inputs (if selected, the IdentityEffect
+just passes its first input unchanged). Similarly, if you set a parameter with
+set_int() or similar, it must be valid for all alternatives (again excepting
+IdentityEffect); if there is one that can only be used on a certain alternative,
+you must set it directly on the effect::
+
+  local resample_effect = ResampleEffect.new()
+  resample_effect:set_float("zoom_x", 1.0001)  -- Not valid for ResizeEffect.
+   
+  local input0_scaled = scene:add_effect({resample_effect, ResizeEffect.new()})
+  input0_scaled:set_int("width", 1280)  -- Set on both alternatives.
+  input0_scaled:set_int("height", 720)
+
+  -- This is also possible, as choose() returns the chosen effect:
+  input0_scaled:choose(ResampleEffect):set_float("zoom_y", 1.0001)
+
 Actually, more versions are created than you'd immediately expect.
 In particular, the output format for the live output and all previews are
 different (Y'CbCr versus RGBA), which is also handled transparently for you.
@@ -248,18 +266,17 @@ preview).
 The number of channels is determined by calling this function
 once at the start of the program::
 
-  function num_channels()
+  Nageru.set_num_channels(2)
 
-It should simply return the number of channels (0 is allowed,
-but doesn't make a lot of sense). Live and preview comes in addition to this.
+0 is allowed, but doesn't make a lot of sense. Live and preview comes in
+addition to this.
 
-Each channel will have a label on it; Nageru asks the theme
-by calling this function::
+Each channel will have a label on it; you set it by calling::
 
-  function channel_name(channel)
+  Nageru.set_channel_name(2, "Side-by-side")
 
-Here, channel is 2, 3, 4, etc.—0 is always called “Live” and
-1 is always called “Preview”.
+Here, channel is 2, 3, 4, etc.—by default, 0 is called “Live” and
+1 is called “Preview”, and you probably don't need to change this.
 
 Each channel has its own scene, starting from number 2 for the first one
 (since 0 is live and 1 is preview). The simplest form is simply a direct copy
@@ -267,24 +284,18 @@ of an input, and most themes will include one such channel for each input.
 (Below, we will see that there are more types of channels, however.)
 Since the mapping between the channel UI element and inputs is so typical,
 Nageru allows the theme to simply declare that a channel corresponds to
-a given signal, by asking it::
-
-  function channel_signal(channel)
-    if channel == 2 then
-      return 0
-    elseif channel == 3 then
-      return 1
-    else
-      return -1
-    end
-  end
+a given signal::
+
+  Nageru.set_channel_signal(2, 0)
+  Nageru.set_channel_signal(3, 1)
 
 Here, channels 2 and 3 (the two first ones) correspond directly to inputs
 0 and 1, respectively. The others don't, and return -1. The effect on the
 UI is that the user can right-click on the channel and configure the input
 that way; in fact, this is currently the only way to configure them.
 
-Furthermore, channels can have a color::
+Furthermore, channels can have a color, which is governed by Nageru calling
+a function your theme::
 
   function channel_color(channel)
  
@@ -294,10 +305,10 @@ the currently playing input as red, or the preview as green.
 
 And finally, there are two entry points related to white balance::
 
-  function supports_set_wb(channel)
+  Nageru.set_supports_wb(2, true)
   function set_wb(channel, red, green, blue)
 
-If the first function returns true (called once, at the start of the program),
+If the first function is called with a true value (at the start of the theme),
 the channel will get a “Set WB” button next to it, which will activate a color
 picker. When the user picks a color (ostensibly with a gray point), the second
 function will be called (with the RGB values in linear light—not sRGB!),
@@ -350,6 +361,51 @@ This allows you to e.g. have simple message overlays that you can change
 without restarting Nageru.
 
 
+.. _locking:
+
+Locking alternatives
+--------------------
+
+In some cases, Nageru may be building in alternatives to a scene that you
+don't really need, resulting in combinatorial explosion. (If the number of
+instances is getting high, you will get a warning when finalizing the scene.)
+For instance, in some cases, you know that a given transition scene will never
+be used for previews, just live. In this case, you can replace the call to
+scene:finalize() with::
+
+  scene:finalize(false)
+
+In this case, you guarantee that the scene will never be returned when
+get_scene() is called with the number 0. (Similarly, you can use true
+to *only* use it for the live channel.)
+
+Similarly, inputs can hold four different input types, but in some scenes,
+you may always use them with a specific one, e.g. an image “bg_img”. In this case,
+you may add the input with a specific type right away::
+
+  scene:add_input(bg_img)
+
+Similarly, for a live input, you can do::
+
+  scene:add_input(0)
+
+You can still use scene:display() to change the input, but it needs to be of
+the same *type* as the one you gave to add_input().
+
+Finally, you can specify that some effects only make sense together, reducing
+the number of possibilities further. For instance, you may have an optional
+crop effect followed by a resample, where the resample is only enabled if the
+crop is. If so, you can do this::
+
+   resample_effect:always_disable_if_disabled(crop_effect)
+
+For more advanced exclusions, you may choose to split up the scenes into several
+distinct ones that you manage yourself; indeed, before Nageru 1.9.0, that was
+the only option. At some point, however, you may choose to simply accept the
+added startup time and a bit of extra RAM cost; ease of use and flexibility often
+trumps such concerns.
+
+
 .. _menus:
 
 Theme menus
@@ -382,8 +438,61 @@ labels and function references::
 When the user chooses a menu entry, the given Lua function will
 automatically be called. There are no arguments nor return values.
 
-There currently is no support for checkboxes, submenus, input boxes
+Menus can contain submenus, by giving an array instead of a function::
+
+  ThemeMenu.set(
+    { "Overlay", {
+       { "Version A", select_overlay_a },
+       { "Version B", select_overlay_b }
+    },
+    { "&Reload overlay", reload_html }
+  )
+
+They can also be checkable, or have checkboxes, by adding a third
+array element containing flags for that::
+
+  ThemeMenu.set(
+    { "Enable overlay",  enable_overlay,  Nageru.CHECKED },    -- Currently checked.
+    { "Enable crashing", make_unstable,   Nageru.CHECKABLE }   -- Can be checked, but isn't currently.
+  )
+
+When such an option is selected, you probably want to rebuild the menu to
+reflect the new state.
+
+There currently is no support for input boxes, sliders,
 or the likes. However, do note that since the theme is written in unrestricted
 Lua, so you can use e.g. `lua-http <https://github.com/daurnimator/lua-http>`_
 to listen for external connections and accept more complicated inputs
 from those.
+
+
+.. _signal-info:
+
+Signal information queries
+--------------------------
+
+As previously mentioned, get_scene() takes in a “signals” parameter
+that you can query for information about each signal (numbered from 0;
+live and preview are channels, not signals), like its current resolution
+or frame rate:
+
+  * get_frame_width(signal), get_frame_height(signal): Width and height of the last frame.
+  * get_width(signal), get_height(signal): Width and height of the last *field*
+    (the field height is half of the frame height for an interlaced signal).
+  * get_interlaced(signal): Whether the last frame was interlaced.
+  * get_has_signal(signal): Whether there is a valid input signal.
+  * get_is_connected(signal): Whether there is even a card connected
+    to this signal (USB cards can be swapped in or out); if not,
+    you will get a stream of single-colored frames.
+  * get_frame_rate_nom(signal), get_frame_rate_den(signal): The frame rate
+    of the last frame, as a rational (e.g. 60/1, or 60000/1001 for 59.94).
+  * get_last_subtitle(signal): See :ref:`subtitle-ingest`.
+  * get_human_readable_resolution(signal): The resolution and frame rate in
+    human-readable form (e.g. “1080i59.94”), suitable for e.g. stream titles.
+    Note that Nageru does not follow the EBU recommendation of using
+    frame rate even for interlaced signals (e.g. “1080i25” instead of “1080i50”),
+    since it is little-used and confusing to most users.
+
+You can use this either for display purposes, or for choosing the right
+effect alternatives. In particular, you may want to disable scaling if
+the frame is already of the correct resolution.