]> git.sesse.net Git - ultimatescore/blobdiff - nageru/ultimate.lua
Change to NM color theme again.
[ultimatescore] / nageru / ultimate.lua
index 63087374033ea701ea42cdc0d7ebcf8514237aa2..34462f2b82219f455537ead2b8cfe97ec34f2465 100644 (file)
@@ -1,4 +1,6 @@
--- Nageru theme for ultimate productions, based on the default theme.
+-- Nageru theme for TFK mini-tournament 2017, based on the default theme.
+
+local futatabi_server = "http://gruessi.trd.sesse.net:9096"
 
 local state = {
        transition_start = -2.0,
@@ -23,6 +25,12 @@ local state = {
        overlay_alpha_dst = 1.0,
        overlay_enabled = false,
 
+       stinger_in_progress = false,
+       stinger_frame = 0,
+       stinger_src_signal = 0,
+       stinger_dst_signal = 0,
+       stinger_save_overlay = false,
+
        live_signal_num = 0,
        preview_signal_num = 1
 }
@@ -39,7 +47,7 @@ local SBS_SIGNAL_NUM = NUM_CAMERAS
 local STATIC_SIGNAL_NUM = NUM_CAMERAS + 1
 local VIDEO_SIGNAL_NUM = NUM_CAMERAS + 2
 
-state.neutral_colors[VIDEO_SIGNAL_NUM - INPUT0_SIGNAL_NUM + 1] = {0.5, 0.5, 0.5};
+state.neutral_colors[VIDEO_SIGNAL_NUM - INPUT0_SIGNAL_NUM + 1] = {0.5, 0.5, 0.5}
 
 -- Preview-only signal showing the current signal with the overlay.
 -- Not valid for live_signal_num!
@@ -61,7 +69,7 @@ cef_input:execute_javascript_async("play()")
 
 local bg_video = VideoInput.new(cef_path .. "/flow-720.mp4", Nageru.VIDEO_FORMAT_YCBCR)
 -- local iptv_video = VideoInput.new("http://10.34.129.69:9060/1/v.flv", Nageru.VIDEO_FORMAT_YCBCR)
-local iptv_video = VideoInput.new("http://home.samfundet.no/~sesse/videostuff", Nageru.VIDEO_FORMAT_YCBCR)
+local iptv_video = VideoInput.new(futatabi_server, Nageru.VIDEO_FORMAT_YCBCR)
 
 function reload_cef()
        cef_input:reload()
@@ -270,11 +278,10 @@ local sbs_chains = make_cartesian_product({
        return make_sbs_chain(input0_type, input0_overlay, input1_type, hq)
 end)
 
--- A chain to show a single input on screen.
-function make_simple_chain(input_deint, input_video, input_scale, has_overlay, hq)
+function make_simple_chain_no_finalize(input_deint, input_video, input_scale, has_overlay, hq)
        local chain = EffectChain.new(16, 9)
 
-       local input;
+       local input
        if input_video then
                input = chain:add_video_input(iptv_video, false)
        else
@@ -294,8 +301,6 @@ function make_simple_chain(input_deint, input_video, input_scale, has_overlay, h
        local wb_effect = chain:add_effect(WhiteBalanceEffect.new())
        local overlay = possibly_make_overlay(has_overlay, chain, wb_effect)
 
-       chain:finalize(hq)
-
        return {
                chain = chain,
                input = input,
@@ -306,6 +311,13 @@ function make_simple_chain(input_deint, input_video, input_scale, has_overlay, h
        }
 end
 
+-- A chain to show a single input on screen.
+function make_simple_chain(input_deint, input_video, input_scale, has_overlay, hq)
+       local chain = make_simple_chain_no_finalize(input_deint, input_video, input_scale, has_overlay, hq)
+       chain.chain:finalize(hq)
+       return chain
+end
+
 -- Make all possible combinations of single-input chains.
 local simple_chains = make_cartesian_product({
        {"video", "live", "livedeint"},  -- input_type
@@ -318,6 +330,36 @@ local simple_chains = make_cartesian_product({
        return make_simple_chain(input_deint, input_video, input_scale, has_overlay, hq)
 end)
 
+-- A chain to show a single input on screen, with a stinger on top.
+function make_stinger_chain(input_deint, input_video, input_scale, has_overlay, stinger_frame, hq)
+       local chain = make_simple_chain_no_finalize(input_deint, input_video, input_scale, has_overlay, hq)
+       local filename = cef_path .. "/stinger/blur" .. string.rep("0", 3 - string.len(tostring(stinger_frame))) .. stinger_frame .. ".png"
+
+       local last
+       if has_overlay then
+               last = chain.overlay.overlay_effect
+       else
+               last = chain.wb_effect
+       end
+       local input = chain.chain:add_effect(ImageInput.new(filename))
+       chain.chain:add_effect(OverlayEffect.new(), last, input)
+       chain.chain:finalize(hq)
+       return chain
+end
+
+-- Single-input chains with various stinger frames on top.
+local stinger_chains = make_cartesian_product({
+       {"video", "live", "livedeint"},  -- input_type
+       {true, false},          -- input_scale
+       {true, false},          -- has_overlay
+       {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24},  -- stinger_frame
+       {true, false}           -- hq
+}, function(input_type, input_scale, has_overlay, stinger_frame, hq)
+       local input_deint = (input_type == "livedeint")
+       local input_video = (input_type == "video")
+       return make_stinger_chain(input_deint, input_video, input_scale, has_overlay, stinger_frame, hq)
+end)
+
 -- A chain to show a single static picture on screen. Never with HTML overlay.
 local static_chains = make_cartesian_product({
        {true, false}            -- hq
@@ -422,6 +464,20 @@ function get_channel_resolution_raw(res)
        end
 end
 
+function get_futatabi_status(str)
+       local num_fields = 0
+       local fields = {}
+       for word in string.gmatch(str, '([^;]+)') do
+               table.insert(fields, word)
+               num_fields = num_fields + 1
+       end
+       if num_fields >= 4 then
+               return fields[4]
+       else
+               return "???"
+       end
+end
+
 -- API ENTRY POINT
 -- Returns the name for each additional channel (starting from 2).
 -- Called at the start of the program, and then each frame for live
@@ -445,7 +501,12 @@ function channel_name(channel)
        elseif signal_num == STATIC_SIGNAL_NUM then
                return "Static picture"
        elseif signal_num == VIDEO_SIGNAL_NUM then
-               return "IPTV input"
+               local res = last_resolution[iptv_video:get_signal_num()]
+               if (not res) or res.last_subtitle == nil then
+                       return "IPTV"
+               else
+                       return "IPTV (" .. get_futatabi_status(res.last_subtitle) .. ")"
+               end
        elseif signal_num == OVERLAY_SIGNAL_NUM then
                return "Overlay"
        end
@@ -574,6 +635,11 @@ function get_transitions(t)
                return {}
        end
 
+       if (is_plain_signal(state.live_signal_num) and state.preview_signal_num == VIDEO_SIGNAL_NUM) or
+          (is_plain_signal(state.preview_signal_num) and state.live_signal_num == VIDEO_SIGNAL_NUM) then
+               return {"Cut", "Sting", "Fade"}
+       end
+
        if (is_plain_signal(state.live_signal_num) or state.live_signal_num == STATIC_SIGNAL_NUM) and
           (is_plain_signal(state.preview_signal_num) or state.preview_signal_num == STATIC_SIGNAL_NUM) then
                return {"Cut", "", "Fade"}
@@ -601,6 +667,7 @@ function start_transition(type_, t, duration)
        state.transition_type = type_
        state.transition_src_signal = state.live_signal_num
        state.transition_dst_signal = state.preview_signal_num
+       state.stinger_in_progress = false
        swap_preview_live()
 end
 
@@ -645,8 +712,9 @@ function transition_clicked(num, t)
                end
 
                swap_preview_live()
+               state.stinger_in_progress = false
        elseif num == 1 then
-               -- Zoom.
+               -- Zoom or sting.
                finish_transitions(t)
 
                if state.live_signal_num == state.preview_signal_num then
@@ -654,10 +722,25 @@ function transition_clicked(num, t)
                        return
                end
 
+               if (is_plain_signal(state.live_signal_num) and state.preview_signal_num == VIDEO_SIGNAL_NUM) or
+                  (is_plain_signal(state.preview_signal_num) and state.live_signal_num == VIDEO_SIGNAL_NUM) then
+                       -- Sting.
+                       if stinger_in_progress then
+                               return
+                       end
+
+                       state.stinger_in_progress = true
+                       state.stinger_frame = 0
+                       state.stinger_src_signal = state.live_signal_num
+                       state.stinger_dst_signal = state.preview_signal_num
+                       return
+               end
+
                if is_plain_signal(state.live_signal_num) and is_plain_signal(state.preview_signal_num) then
                        -- We can't zoom between these. Just make a cut.
                        io.write("Cutting from " .. state.live_signal_num .. " to " .. state.live_signal_num .. "\n")
                        swap_preview_live()
+                       state.stinger_in_progress = false
                        return
                end
 
@@ -734,6 +817,31 @@ function get_sbs_chain(state, signals)
        return sbs_chains[input0_type][state.overlay_enabled][input1_type][true]
 end
 
+function fetch_input_resolution(signals, signal_num)
+       local res = {
+               width = signals:get_width(signal_num),
+               height = signals:get_height(signal_num),
+               interlaced = signals:get_interlaced(signal_num),
+               has_signal = signals:get_has_signal(signal_num),
+               is_connected = signals:get_is_connected(signal_num),
+               frame_rate_nom = signals:get_frame_rate_nom(signal_num),
+               frame_rate_den = signals:get_frame_rate_den(signal_num),
+               last_subtitle = signals:get_last_subtitle(signal_num)
+       }
+
+       if res.interlaced then
+               -- Convert height from frame height to field height.
+               -- (Needed for e.g. place_rectangle.)
+               res.height = res.height * 2
+
+               -- Show field rate instead of frame rate; really for cosmetics only
+               -- (and actually contrary to EBU recommendations, although in line
+               -- with typical user expectations).
+               res.frame_rate_nom = res.frame_rate_nom * 2
+       end
+       return res
+end
+
 local last_rate = 0.0
 
 -- API ENTRY POINT
@@ -763,29 +871,9 @@ local last_rate = 0.0
 function get_chain(num, t, width, height, signals)
        local input_resolution = {}
        for signal_num=0,(NUM_CAMERAS-1) do
-               local res = {
-                       width = signals:get_width(signal_num),
-                       height = signals:get_height(signal_num),
-                       interlaced = signals:get_interlaced(signal_num),
-                       has_signal = signals:get_has_signal(signal_num),
-                       is_connected = signals:get_is_connected(signal_num),
-                       frame_rate_nom = signals:get_frame_rate_nom(signal_num),
-                       frame_rate_den = signals:get_frame_rate_den(signal_num)
-               }
-
-               if res.interlaced then
-                       -- Convert height from frame height to field height.
-                       -- (Needed for e.g. place_rectangle.)
-                       res.height = res.height * 2
-
-                       -- Show field rate instead of frame rate; really for cosmetics only
-                       -- (and actually contrary to EBU recommendations, although in line
-                       -- with typical user expectations).
-                       res.frame_rate_nom = res.frame_rate_nom * 2
-               end
-
-               input_resolution[signal_num] = res
+               input_resolution[signal_num] = fetch_input_resolution(signals, signal_num)
        end
+       input_resolution[iptv_video:get_signal_num()] = fetch_input_resolution(signals, iptv_video:get_signal_num())
        last_resolution = input_resolution
 
        -- Make a (semi-shallow) copy of the current state, so that the returned prepare function
@@ -820,7 +908,7 @@ function get_chain(num, t, width, height, signals)
                        -- Transition in or out of SBS.
                        local chain = get_sbs_chain(state_copy, signals)
                        local prepare = function()
-                               prepare_sbs_chain(state_copy, chain, calc_zoom_progress(t), state_copy.transition_type, state_copy.transition_src_signal, state.transition_dst_signal, width, height, input_resolution)
+                               prepare_sbs_chain(state_copy, chain, calc_zoom_progress(t), state_copy.transition_type, state_copy.transition_src_signal, state_copy.transition_dst_signal, width, height, input_resolution)
                                prepare_overlay_live(state_copy, chain, t, 1.0)
                        end
                        return chain.chain, prepare
@@ -837,8 +925,28 @@ function get_chain(num, t, width, height, signals)
                elseif is_plain_signal(state.live_signal_num) then
                        local input_type = get_input_type(signals, state.live_signal_num)
                        local input_scale = needs_scale(signals, state.live_signal_num, width, height)
-                       local overlay_really_enabled = state_copy.overlay_enabled and simple_signal_has_overlay(state.live_signal_num)
-                       local chain = simple_chains[input_type][input_scale][overlay_really_enabled][true]
+                       local overlay_really_enabled = state.overlay_enabled and simple_signal_has_overlay(state.live_signal_num)
+                       local chain
+                       if state.stinger_in_progress then
+                               chain = stinger_chains[input_type][input_scale][overlay_really_enabled][state.stinger_frame][true]
+                               state.stinger_frame = state.stinger_frame + 1
+                               if state.stinger_frame >= 25 then
+                                       state.stinger_in_progress = false
+                                       state.preview_signal_num = state.stinger_src_signal
+                                       state.live_signal_num = state.stinger_dst_signal
+
+                                       if state.stinger_dst_signal == VIDEO_SIGNAL_NUM then
+                                               -- Turn off the overlay when playing video.
+                                               state.stinger_save_overlay = state.overlay_enabled
+                                               state.overlay_enabled = false
+                                       else
+                                               -- Restore the state.
+                                               state.overlay_enabled = state.stinger_save_overlay
+                                       end
+                               end
+                       else
+                               chain = simple_chains[input_type][input_scale][overlay_really_enabled][true]
+                       end
                        local prepare = function()
                                if input_type ~= "video" then
                                        chain.input:connect_signal(state.live_signal_num)