X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;ds=sidebyside;f=nageru%2Fultimate.lua;h=34462f2b82219f455537ead2b8cfe97ec34f2465;hb=84e66e56f5a339cab7bf9cceab297358ba6c49bc;hp=63087374033ea701ea42cdc0d7ebcf8514237aa2;hpb=a5e8345fcfe628521d5d6e1e359456c9cb7e8ec0;p=ultimatescore diff --git a/nageru/ultimate.lua b/nageru/ultimate.lua index 6308737..34462f2 100644 --- a/nageru/ultimate.lua +++ b/nageru/ultimate.lua @@ -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)