]> git.sesse.net Git - ultimatescore/blobdiff - nageru/ultimate.lua
Check in some scripts based on OR-tools to try to generate good group schedules.
[ultimatescore] / nageru / ultimate.lua
index 63087374033ea701ea42cdc0d7ebcf8514237aa2..8b2b7f1b42e27f33432ec2307e7284c390b3da4f 100644 (file)
@@ -1,4 +1,9 @@
--- Nageru theme for ultimate productions, based on the default theme.
+-- Nageru theme for TFK mini-tournament 2017, based on the default theme.
+
+local http_request = require("http.request")
+local cqueues = require("cqueues")
+local cq = cqueues.new()
+local futatabi_server = "http://gruessi.trd.sesse.net:9095"
 
 local state = {
        transition_start = -2.0,
@@ -23,11 +28,32 @@ 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
 }
 local NUM_CAMERAS = 6  -- Remember to update neutral_colors, too.
 
+-- Update the Futatabi status in the title.
+replay_title = "IPTV"
+cq:wrap(function()
+       while true do
+               local headers, stream = assert(http_request.new_from_uri(futatabi_server .. "/queue_status"):go())
+               replay_title = "IPTV"
+               if headers:get ":status" == "200" then
+                       local body = assert(stream:get_body_as_string())
+                       if body then
+                               replay_title = "IPTV (" .. body .. ")"
+                       end
+               end
+       end
+end)
+
 -- Valid values for live_signal_num and preview_signal_num.
 local INPUT0_SIGNAL_NUM = 0
 local INPUT1_SIGNAL_NUM = 1
@@ -39,7 +65,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 +87,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 +296,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 +319,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 +329,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 +348,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
@@ -445,7 +505,8 @@ function channel_name(channel)
        elseif signal_num == STATIC_SIGNAL_NUM then
                return "Static picture"
        elseif signal_num == VIDEO_SIGNAL_NUM then
-               return "IPTV input"
+               -- return "IPTV input (" .. get_channel_resolution(iptv_video:get_signal_num()) .. ")"
+               return replay_title
        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,30 @@ 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)
+       }
+
+       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
@@ -761,31 +868,13 @@ local last_rate = 0.0
 -- NOTE: The chain returned must be finalized with the Y'CbCr flag
 -- if and only if num==0.
 function get_chain(num, t, width, height, signals)
+       cq:loop(0)
+
        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 +909,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 +926,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)