-- 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,
transition_end = -1.0,
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 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()
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 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,
}
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
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
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
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
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"}
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
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
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
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
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
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.overlay_enabled and simple_signal_has_overlay(state.live_signal_num)
- local chain = simple_chains[input_type][input_scale][overlay_really_enabled][true]
+ 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)