X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=nageru%2Fultimate.lua;h=c0c8be1f7aec2575ebe47028ee64fc634670a182;hb=37bfa25c22128f431b10da8c25e7081b5ea1f6a7;hp=a9991d111d68f03333c96c4883454588b4080d5c;hpb=4b5358cadbfbbfc4a65eaa035afaaa7411210750;p=ultimatescore diff --git a/nageru/ultimate.lua b/nageru/ultimate.lua index a9991d1..c0c8be1 100644 --- a/nageru/ultimate.lua +++ b/nageru/ultimate.lua @@ -1,29 +1,39 @@ --- Nageru theme for ultimate productions, based on the default theme. - -local transition_start = -2.0 -local transition_end = -1.0 -local transition_type = 0 -local transition_src_signal = 0 -local transition_dst_signal = 0 - -local neutral_colors = { - {0.5, 0.5, 0.5}, -- Input 0. - {0.5, 0.5, 0.5}, -- Input 1. - {0.5, 0.5, 0.5}, -- Input 2. - {0.5, 0.5, 0.5}, -- Input 3. - {0.5, 0.5, 0.5}, -- Input 4. - {0.5, 0.5, 0.5} -- Input 5. - -- Will also be filled with VIDEO_SIGNAL_NUM below. +-- 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, + transition_type = 0, + transition_src_signal = 0, + transition_dst_signal = 0, + + neutral_colors = { + {0.5, 0.5, 0.5}, -- Input 0. + {0.5, 0.5, 0.5}, -- Input 1. + {0.5, 0.5, 0.5}, -- Input 2. + {0.5, 0.5, 0.5}, -- Input 3. + {0.5, 0.5, 0.5}, -- Input 4. + {0.5, 0.5, 0.5} -- Input 5. + -- Will also be filled with VIDEO_SIGNAL_NUM below. + }, + + overlay_transition_start = -2.0, + overlay_transition_end = -1.0, + overlay_alpha_src = 0.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 overlay_transition_start = -2.0 -local overlay_transition_end = -1.0 -local overlay_alpha_src = 0.0 -local overlay_alpha_dst = 1.0 -local overlay_enabled = false - -local live_signal_num = 0 -local preview_signal_num = 1 local NUM_CAMERAS = 6 -- Remember to update neutral_colors, too. -- Valid values for live_signal_num and preview_signal_num. @@ -37,7 +47,7 @@ local SBS_SIGNAL_NUM = NUM_CAMERAS local STATIC_SIGNAL_NUM = NUM_CAMERAS + 1 local VIDEO_SIGNAL_NUM = NUM_CAMERAS + 2 -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! @@ -59,7 +69,10 @@ 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) +iptv_video:change_rate(10.0) + +local static_image = ImageInput.new(cef_path .. "/nageru/dsn-bg.png") function reload_cef() cef_input:reload() @@ -70,29 +83,11 @@ function disconnect_iptv() iptv_video:disconnect() end --- Utility function to help creating many similar chains that can differ --- in a free set of chosen parameters. -function make_cartesian_product(parms, callback) - return make_cartesian_product_internal(parms, callback, 1, {}) -end - -function make_cartesian_product_internal(parms, callback, index, args) - if index > #parms then - return callback(unpack(args)) - end - local ret = {} - for _, value in ipairs(parms[index]) do - args[index] = value - ret[value] = make_cartesian_product_internal(parms, callback, index + 1, args) - end - return ret -end - -- An overlay with variable alpha. -function make_overlay(chain, base) - local image = chain:add_html_input(cef_input) - local multiply_effect = chain:add_effect(MultiplyEffect.new()) - local overlay_effect = chain:add_effect(OverlayEffect.new(), base, multiply_effect) +function make_overlay(scene, base) + local image = scene:add_input(cef_input) + local multiply_effect = scene:add_effect(MultiplyEffect.new()) + local overlay_effect = scene:add_optional_effect(OverlayEffect.new(), base, multiply_effect) return { image = image, multiply_effect = multiply_effect, @@ -100,252 +95,140 @@ function make_overlay(chain, base) } end -function possibly_make_overlay(has_overlay, chain, base) - if has_overlay == true then - return make_overlay(chain, base) - else - return nil - end -end - -function make_fade_input(chain, signal, live, deint, scale, video) - local input, wb_effect, resample_effect, last - if video then - input = chain:add_video_input(iptv_video, false) - last = input - elseif live then - input = chain:add_live_input(false, deint) - input:connect_signal(signal) - last = input - else - input = chain:add_effect(ImageInput.new(cef_path .. "/nageru/dsn-bg.png")) - last = input - end - - -- If we cared about this for the non-main inputs, we would have - -- checked hq here and invoked ResizeEffect instead. - if scale then - resample_effect = chain:add_effect(ResampleEffect.new()) - last = resample_effect - end - - -- Make sure to put the white balance after the scaling (usually more efficient). - if live then - wb_effect = chain:add_effect(WhiteBalanceEffect.new()) - last = wb_effect - end - - return { - input = input, - wb_effect = wb_effect, - resample_effect = resample_effect, - last = last +function make_fade_input(scene) + local ret = { + input = scene:add_input(), + resample_effect = scene:add_optional_effect(ResampleEffect.new()), -- Activated if scaling. + wb_effect = scene:add_optional_effect(WhiteBalanceEffect.new()) -- Activated for video inputs. } + ret.overlay = make_overlay(scene, ret.wb_effect) + return ret end --- A chain to fade between two inputs, of which either can be a picture --- or a live input. In practice only used live, but we still support the --- hq parameter. -function make_fade_chain(input0_live, input0_deint, input0_scale, input0_video, input1_live, input1_deint, input1_scale, input1_video, has_overlay, hq) - local chain = EffectChain.new(16, 9) - - local input0 = make_fade_input(chain, INPUT0_SIGNAL_NUM, input0_live, input0_deint, input0_scale, input0_video) - local input1 = make_fade_input(chain, INPUT1_SIGNAL_NUM, input1_live, input1_deint, input1_scale, input1_video) +-- A scene to fade between two inputs, of which either can be a picture +-- or a live input. +function make_fade_scene() + local scene = Scene.new(16, 9) -- If fading between two live inputs, the overlay is put on top. -- If fading between the static picture and a live input, -- the overlay is put on the live input. - local overlay = nil - if input0_live and not input1_live then - overlay = possibly_make_overlay(has_overlay, chain, input0.last) - if overlay then - input0.last = overlay.overlay_effect - end - elseif input1_live and not input0_live then - overlay = possibly_make_overlay(has_overlay, chain, input1.last) - if overlay then - input1.last = overlay.overlay_effect - end - end + local input0 = make_fade_input(scene) + local input1 = make_fade_input(scene) + local mix_effect = scene:add_effect(MixEffect.new(), input0.overlay.overlay_effect, input1.overlay.overlay_effect) + local overlay = make_overlay(scene, mix_effect) - local mix_effect = chain:add_effect(MixEffect.new(), input0.last, input1.last) - if input0_live and input1_live then - overlay = possibly_make_overlay(has_overlay, chain, mix_effect) - end + -- At most one overlay can be active at any given time. + input0.overlay.overlay_effect:promise_to_disable_if_enabled(input1.overlay.overlay_effect) + input0.overlay.overlay_effect:promise_to_disable_if_enabled(overlay.overlay_effect) + input1.overlay.overlay_effect:promise_to_disable_if_enabled(input0.overlay.overlay_effect) + input1.overlay.overlay_effect:promise_to_disable_if_enabled(overlay.overlay_effect) + overlay.overlay_effect:promise_to_disable_if_enabled(input0.overlay.overlay_effect) + overlay.overlay_effect:promise_to_disable_if_enabled(input1.overlay.overlay_effect) - chain:finalize(hq) + scene:finalize(true) -- Only used live, don't bother creating LQ versions. return { - chain = chain, + scene = scene, input0 = input0, input1 = input1, mix_effect = mix_effect, overlay = overlay } end +fade_scene = make_fade_scene() --- Chains to fade between two inputs, in various configurations. -local fade_chains = make_cartesian_product({ - {"video", "static", "live", "livedeint"}, -- input0_type - {true, false}, -- input0_scale - {"video", "static", "live", "livedeint"}, -- input1_type - {true, false}, -- input1_scale - {true, false}, -- has_overlay - {true} -- hq -}, function(input0_type, input0_scale, input1_type, input1_scale, has_overlay, hq) - local input0_live = (input0_type ~= "static") - local input1_live = (input1_type ~= "static") - local input0_deint = (input0_type == "livedeint") - local input1_deint = (input1_type == "livedeint") - local input0_video = (input0_type == "video") - local input1_video = (input1_type == "video") - return make_fade_chain(input0_live, input0_deint, input0_scale, input0_video, input1_live, input1_deint, input1_scale, input1_video, has_overlay, hq) -end) - -function make_sbs_input(chain, signal, deint, has_overlay, hq) - local input = chain:add_live_input(not deint, deint) -- Override bounce only if not deinterlacing. - input:connect_signal(signal) - - local overlay = nil - if has_overlay then - overlay = make_overlay(chain, input) - end - - local resample_effect = nil - local resize_effect = nil - if (hq) then - resample_effect = chain:add_effect(ResampleEffect.new()) - else - resize_effect = chain:add_effect(ResizeEffect.new()) - end - local wb_effect = chain:add_effect(WhiteBalanceEffect.new()) - - local padding_effect = chain:add_effect(IntegralPaddingEffect.new()) - +function make_sbs_input(scene, has_overlay) + local input = scene:add_input() return { input = input, - wb_effect = wb_effect, - resample_effect = resample_effect, - resize_effect = resize_effect, - padding_effect = padding_effect, - overlay = overlay + overlay = has_overlay and make_overlay(scene, input) or nil, + resample_effect = scene:add_effect({ResampleEffect.new(), ResizeEffect.new()}), + wb_effect = scene:add_effect(WhiteBalanceEffect.new()), + padding_effect = scene:add_effect(IntegralPaddingEffect.new()) } end --- Side-by-side chains. -function make_sbs_chain(input0_type, input0_overlay, input1_type, hq) - local chain = EffectChain.new(16, 9) - - local bg = chain:add_video_input(bg_video, false) +-- Side-by-side scene. +function make_sbs_scene() + local scene = Scene.new(16, 9) - local input0 = make_sbs_input(chain, INPUT0_SIGNAL_NUM, input0_type == "livedeint", input0_overlay, hq) - local input1 = make_sbs_input(chain, INPUT4_SIGNAL_NUM, input1_type == "livedeint", false, hq) + local bg = scene:add_input(bg_video) + local input0 = make_sbs_input(scene, true) + local input1 = make_sbs_input(scene, false) input0.padding_effect:set_vec4("border_color", 0.0, 0.0, 0.0, 0.0) input1.padding_effect:set_vec4("border_color", 0.0, 0.0, 0.0, 0.0) - local i0 = chain:add_effect(OverlayEffect.new(), bg, input0.padding_effect) - chain:add_effect(OverlayEffect.new(), i0, input1.padding_effect) - chain:finalize(hq) + input0.input:display(0) + input1.input:display(1) + + local i0 = scene:add_effect(OverlayEffect.new(), bg, input0.padding_effect) + scene:add_effect(OverlayEffect.new(), i0, input1.padding_effect) + scene:finalize() return { - chain = chain, + scene = scene, input0 = input0, input1 = input1, overlay = input0.overlay -- May be nil. } end +local sbs_scene = make_sbs_scene() +function make_simple_scene_no_finalize() + local scene = Scene.new(16, 9) --- Make all possible combinations of side-by-side chains. -local sbs_chains = make_cartesian_product({ - {"live", "livedeint"}, -- input0_type - {true, false}, -- input0_overlay - {"live", "livedeint"}, -- input1_type - {true, false} -- hq -}, function(input0_type, input0_overlay, input1_type, hq) - 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) - local chain = EffectChain.new(16, 9) - - local input; - if input_video then - input = chain:add_video_input(iptv_video, false) - else - input = chain:add_live_input(false, input_deint) - input:connect_signal(0) -- First input card. Can be changed whenever you want. - end - - local resample_effect, resize_effect - if scale then - if hq then - resample_effect = chain:add_effect(ResampleEffect.new()) - else - resize_effect = chain:add_effect(ResizeEffect.new()) - end - end - - local wb_effect = chain:add_effect(WhiteBalanceEffect.new()) - local overlay = possibly_make_overlay(has_overlay, chain, wb_effect) - - chain:finalize(hq) + local input = scene:add_input() + local resample_effect = scene:add_effect({ResampleEffect.new(), ResizeEffect.new(), IdentityEffect.new()}) + local wb_effect = scene:add_effect(WhiteBalanceEffect.new()) + local overlay = make_overlay(scene, wb_effect) return { - chain = chain, + scene = scene, input = input, wb_effect = wb_effect, resample_effect = resample_effect, - resize_effect = resize_effect, overlay = overlay } end --- Make all possible combinations of single-input chains. -local simple_chains = make_cartesian_product({ - {"video", "live", "livedeint"}, -- input_type - {true, false}, -- input_scale - {true, false}, -- has_overlay - {true, false} -- hq -}, function(input_type, input_scale, has_overlay, hq) - local input_deint = (input_type == "livedeint") - local input_video = (input_type == "video") - return make_simple_chain(input_deint, input_video, input_scale, has_overlay, 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 -}, function(hq) - local chain = EffectChain.new(16, 9) - local chain_input = chain:add_effect(ImageInput.new(cef_path .. "/nageru/dsn-bg.png")) - - chain:finalize(hq) - return { - chain = chain - } -end) +-- A scene to show a single input on screen. +function make_simple_scene() + local scene = make_simple_scene_no_finalize() + scene.scene:finalize() + return scene +end --- A chain to show the overlay and nothing more. LQ only, --- since it is not a valid live signal. -local overlay_chain_lq = EffectChain.new(16, 9) -local overlay_chain_lq_input = overlay_chain_lq:add_html_input(cef_input) -overlay_chain_lq:finalize(false) +local simple_scene = make_simple_scene() --- Used for indexing into the tables of chains. -function get_input_type(signals, signal_num) - if signal_num == STATIC_SIGNAL_NUM then - return "static" - elseif signal_num == VIDEO_SIGNAL_NUM then - return "video" - elseif signals:get_interlaced(signal_num) then - return "livedeint" - else - return "live" - end +-- Load all the stinger frames. +local stinger_images = {} +for idx=0,24 do + local filename = cef_path .. "/stinger/blur" .. string.rep("0", 3 - string.len(tostring(idx))) .. idx .. ".png" + stinger_images[idx] = ImageInput.new(filename) +end + +-- A scene to show a single input on screen, with a stinger on top. +function make_stinger_scene() + local scene = make_simple_scene_no_finalize() + scene.stinger_input = scene.scene:add_input() + scene.scene:add_effect(OverlayEffect.new(), scene.overlay.overlay_effect, scene.stinger_input) + scene.scene:finalize() + return scene end +local stinger_scene = make_stinger_scene() + +-- A scene to show a single static picture on screen. Never with HTML overlay. +local static_scene = Scene.new(16, 9) +static_scene:add_input(static_image) +static_scene:finalize() + +-- A scene to show the overlay and nothing more. LQ only, +-- since it is not a valid live signal. +local overlay_scene_lq = Scene.new(16, 9) +local overlay_scene_lq_input = overlay_scene_lq:add_input(cef_input) +overlay_scene_lq:finalize(false) function needs_scale(signals, signal_num, width, height) if signal_num == STATIC_SIGNAL_NUM then @@ -356,14 +239,26 @@ function needs_scale(signals, signal_num, width, height) return (signals:get_width(signal_num) ~= width or signals:get_height(signal_num) ~= height) end -function set_scale_parameters_if_needed(chain_or_input, width, height) - if chain_or_input.resample_effect then - chain_or_input.resample_effect:set_int("width", width) - chain_or_input.resample_effect:set_int("height", height) - elseif chain_or_input.resize_effect then - chain_or_input.resize_effect:set_int("width", width) - chain_or_input.resize_effect:set_int("height", height) +function set_scale_parameters_if_needed(scene, signals, signal_num, width, height, hq) + -- Futatabi rescaling will be linear, but original frames come back unscaled; + -- make them linear, too, so that we don't get more sharpness on every other + -- frame. This won't be right for fading, but it's probably hard to notice. + if signal_num == VIDEO_SIGNAL_NUM then + hq = false + end + + if needs_scale(signals, signal_num, width, height) then + if hq then + scene.resample_effect:choose(ResampleEffect) -- High-quality resampling. + else + scene.resample_effect:choose(ResizeEffect) -- Low-quality resampling. + end + scene.resample_effect:set_int("width", width) + scene.resample_effect:set_int("height", height) + else + scene.resample_effect:disable() end + end -- API ENTRY POINT @@ -381,7 +276,7 @@ end -- and get_channel_resolution_raw() is that this one also can say that -- there's no signal. function get_channel_resolution(signal_num) - res = last_resolution[signal_num] + local res = last_resolution[signal_num] if (not res) or not res.is_connected then return "disconnected" end @@ -419,6 +314,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 @@ -442,7 +351,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 "Futatabi" + else + return "Futatabi (" .. get_futatabi_status(res.last_subtitle) .. ")" + end elseif signal_num == OVERLAY_SIGNAL_NUM then return "Overlay" end @@ -471,17 +385,17 @@ end -- "transparent" is allowed. -- Will never be called for live (0) or preview (1). function channel_color(channel) - if transition_type ~= NO_TRANSITION then - if channel_involved_in(channel, transition_src_signal) or - channel_involved_in(channel, transition_dst_signal) then + if state.transition_type ~= NO_TRANSITION then + if channel_involved_in(channel, state.transition_src_signal) or + channel_involved_in(channel, state.transition_dst_signal) then return "#f00" end else - if channel_involved_in(channel, live_signal_num) then + if channel_involved_in(channel, state.live_signal_num) then return "#f00" end end - if channel_involved_in(channel, preview_signal_num) then + if channel_involved_in(channel, state.preview_signal_num) then return "#0f0" end return "transparent" @@ -515,25 +429,25 @@ end -- The color is in linear light (not sRGB gamma). function set_wb(channel, red, green, blue) if is_plain_signal(channel - 2) then - neutral_colors[channel - 2 + 1] = { red, green, blue } + state.neutral_colors[channel - 2 + 1] = { red, green, blue } end end function finish_transitions(t) - if transition_type ~= NO_TRANSITION and t >= transition_end then - live_signal_num = transition_dst_signal - transition_type = NO_TRANSITION + if state.transition_type ~= NO_TRANSITION and t >= state.transition_end then + state.live_signal_num = state.transition_dst_signal + state.transition_type = NO_TRANSITION end -- Disable the overlay if it is no longer visible. - if overlay_enabled and t > overlay_transition_end and overlay_alpha_dst == 0.0 then - overlay_enabled = false + if state.overlay_enabled and t > state.overlay_transition_end and state.overlay_alpha_dst == 0.0 then + state.overlay_enabled = false print("Turning off overlay") end end function in_transition(t) - return t >= transition_start and t <= transition_end + return t >= state.transition_start and t <= state.transition_end end function is_sbs_participating_signal(signal_num) @@ -548,12 +462,12 @@ end -- API ENTRY POINT -- Called every frame. function get_transitions(t) - if preview_signal_num == OVERLAY_SIGNAL_NUM then - if t < overlay_transition_end then + if state.preview_signal_num == OVERLAY_SIGNAL_NUM then + if t < state.overlay_transition_end then -- Fade in progress. return {} end - if overlay_enabled then + if state.overlay_enabled then return {"Overlay off", "", "Fade ovl out"} else return {"Overlay on", "", "Fade ovl in"} @@ -566,20 +480,25 @@ function get_transitions(t) return {"Cut"} end - if live_signal_num == preview_signal_num then + if state.live_signal_num == state.preview_signal_num then -- No transitions possible. return {} end - if (is_plain_signal(live_signal_num) or live_signal_num == STATIC_SIGNAL_NUM) and - (is_plain_signal(preview_signal_num) or preview_signal_num == STATIC_SIGNAL_NUM) then + 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"} end -- Various zooms. - if live_signal_num == SBS_SIGNAL_NUM and is_sbs_participating_signal(preview_signal_num) then + if state.live_signal_num == SBS_SIGNAL_NUM and is_sbs_participating_signal(state.preview_signal_num) then return {"Cut", "Zoom in"} - elseif is_sbs_participating_signal(live_signal_num) and preview_signal_num == SBS_SIGNAL_NUM then + elseif is_sbs_participating_signal(state.live_signal_num) and state.preview_signal_num == SBS_SIGNAL_NUM then return {"Cut", "Zoom out"} end @@ -587,49 +506,50 @@ function get_transitions(t) end function swap_preview_live() - local temp = live_signal_num - live_signal_num = preview_signal_num - preview_signal_num = temp + local temp = state.live_signal_num + state.live_signal_num = state.preview_signal_num + state.preview_signal_num = temp end function start_transition(type_, t, duration) - transition_start = t - transition_end = t + duration - transition_type = type_ - transition_src_signal = live_signal_num - transition_dst_signal = preview_signal_num + state.transition_start = t + state.transition_end = 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 -- API ENTRY POINT -- Called when the user clicks a transition button. function transition_clicked(num, t) - if preview_signal_num == OVERLAY_SIGNAL_NUM then + if state.preview_signal_num == OVERLAY_SIGNAL_NUM then if num == 0 then -- Cut. - overlay_transition_start = -2.0 - overlay_transition_end = -1.0 - if overlay_enabled then - overlay_enabled = false - overlay_alpha_src = 0.0 - overlay_alpha_dst = 0.0 + state.overlay_transition_start = -2.0 + state.overlay_transition_end = -1.0 + if state.overlay_enabled then + state.overlay_enabled = false + state.overlay_alpha_src = 0.0 + state.overlay_alpha_dst = 0.0 else - overlay_enabled = true - overlay_alpha_src = 1.0 - overlay_alpha_dst = 1.0 + state.overlay_enabled = true + state.overlay_alpha_src = 1.0 + state.overlay_alpha_dst = 1.0 end elseif num == 2 then -- Fade. - overlay_transition_start = t - overlay_transition_end = t + 1.0 - if overlay_enabled then - overlay_alpha_src = 1.0 - overlay_alpha_dst = 0.0 + state.overlay_transition_start = t + state.overlay_transition_end = t + 1.0 + if state.overlay_enabled then + state.overlay_alpha_src = 1.0 + state.overlay_alpha_dst = 0.0 else - overlay_alpha_src = 0.0 - overlay_alpha_dst = 1.0 + state.overlay_alpha_src = 0.0 + state.overlay_alpha_dst = 1.0 end - overlay_enabled = true + state.overlay_enabled = true end return end @@ -638,103 +558,161 @@ function transition_clicked(num, t) -- Cut. if in_transition(t) then -- Ongoing transition; finish it immediately before the cut. - finish_transitions(transition_end) + finish_transitions(state.transition_end) end swap_preview_live() + state.stinger_in_progress = false elseif num == 1 then - -- Zoom. + -- Zoom or sting. finish_transitions(t) - if live_signal_num == preview_signal_num then + if state.live_signal_num == state.preview_signal_num then -- Nothing to do. return end - if is_plain_signal(live_signal_num) and is_plain_signal(preview_signal_num) then + 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 " .. live_signal_num .. " to " .. live_signal_num .. "\n") + io.write("Cutting from " .. state.live_signal_num .. " to " .. state.live_signal_num .. "\n") swap_preview_live() + state.stinger_in_progress = false return end - if (live_signal_num == SBS_SIGNAL_NUM and is_sbs_participating_signal(preview_signal_num)) or - (preview_signal_num == SBS_SIGNAL_NUM and is_sbs_participating_signal(live_signal_num)) then + if (state.live_signal_num == SBS_SIGNAL_NUM and is_sbs_participating_signal(state.preview_signal_num)) or + (state.preview_signal_num == SBS_SIGNAL_NUM and is_sbs_participating_signal(state.live_signal_num)) then start_transition(ZOOM_TRANSITION, t, 1.0) end elseif num == 2 then finish_transitions(t) -- Fade. - if (live_signal_num ~= preview_signal_num) and - (is_plain_signal(live_signal_num) or - live_signal_num == STATIC_SIGNAL_NUM) and - (is_plain_signal(preview_signal_num) or - preview_signal_num == STATIC_SIGNAL_NUM) then + if (state.live_signal_num ~= state.preview_signal_num) and + (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 start_transition(FADE_TRANSITION, t, 1.0) else - -- Fades involving SBS are ignored (we have no chain for it). + -- Fades involving SBS are ignored (we have no scene for it). end end end -- API ENTRY POINT function channel_clicked(num) - preview_signal_num = num + state.preview_signal_num = num end -function get_fade_chain(signals, t, width, height, input_resolution) - local input0_type = get_input_type(signals, transition_src_signal) - local input0_scale = needs_scale(signals, transition_src_signal, width, height) - local input1_type = get_input_type(signals, transition_dst_signal) - local input1_scale = needs_scale(signals, transition_dst_signal, width, height) - local chain = fade_chains[input0_type][input0_scale][input1_type][input1_scale][overlay_enabled][true] - prepare = function() - if input0_type == "live" or input0_type == "livedeint" then - chain.input0.input:connect_signal(transition_src_signal) - end - if input0_type ~= "static" then - set_neutral_color_from_signal(chain.input0.wb_effect, transition_src_signal) - end - set_scale_parameters_if_needed(chain.input0, width, height) - if input1_type == "live" or input1_type == "livedeint" then - chain.input1.input:connect_signal(transition_dst_signal) - end - if input1_type ~= "static" then - set_neutral_color_from_signal(chain.input1.wb_effect, transition_dst_signal) +function setup_fade_input(state, input, signals, signal_num, width, height) + if signal_num == STATIC_SIGNAL_NUM then + input.input:display(static_image) + input.wb_effect:disable() + + -- We assume this is already correctly scaled at load time. + input.resample_effect:disable() + else + input.input:display(signal_num) + input.wb_effect:enable() + set_neutral_color(input.wb_effect, state.neutral_colors[signal_num - INPUT0_SIGNAL_NUM + 1]) + + if (signals:get_width(signal_num) ~= width or signals:get_height(signal_num) ~= height) then + input.resample_effect:enable() + input.resample_effect:set_int("width", width) + input.resample_effect:set_int("height", height) + else + input.resample_effect:disable() end - set_scale_parameters_if_needed(chain.input1, width, height) - local tt = calc_fade_progress(t, transition_start, transition_end) - - chain.mix_effect:set_float("strength_first", 1.0 - tt) - chain.mix_effect:set_float("strength_second", tt) - - -- The commentator output has no overlay on it. - local extra_alpha_factor = 1.0 - if not simple_signal_has_overlay(transition_src_signal) and - not simple_signal_has_overlay(transition_dst_signal) then - extra_alpha_factor = 0.0 - elseif not simple_signal_has_overlay(transition_src_signal) then - extra_alpha_factor = tt - elseif not simple_signal_has_overlay(transition_dst_signal) then - extra_alpha_factor = 1.0 - tt + end +end + +function get_fade_scene(state, signals, t, width, height, input_resolution) + local scene = fade_scene + setup_fade_input(state, scene.input0, signals, state.transition_src_signal, width, height) + setup_fade_input(state, scene.input1, signals, state.transition_dst_signal, width, height) + + local tt = calc_fade_progress(t, state.transition_start, state.transition_end) + scene.mix_effect:set_float("strength_first", 1.0 - tt) + scene.mix_effect:set_float("strength_second", tt) + + -- The commentator output has no overlay on it. + local extra_alpha_factor = 1.0 + if not simple_signal_has_overlay(state.transition_src_signal) and + not simple_signal_has_overlay(state.transition_dst_signal) then + extra_alpha_factor = 0.0 + elseif not simple_signal_has_overlay(state.transition_src_signal) then + extra_alpha_factor = tt + elseif not simple_signal_has_overlay(state.transition_dst_signal) then + extra_alpha_factor = 1.0 - tt + end + + -- If fading between two live inputs, the overlay is put on top. + -- If fading between the static picture and a live input, + -- the overlay is put on the live input. + scene.input0.overlay.overlay_effect:disable() + scene.input1.overlay.overlay_effect:disable() + scene.overlay.overlay_effect:disable() + if state.overlay_enabled then + local input0_live = (state.transition_src_signal ~= STATIC_SIGNAL_NUM) + local input1_live = (state.transition_dst_signal ~= STATIC_SIGNAL_NUM) + if input0_live and not input1_live then + scene.input0.overlay.overlay_effect:enable() + prepare_overlay_live(state, scene.input0, t, extra_alpha_factor) + elseif input1_live and not input0_live then + scene.input1.overlay.overlay_effect:enable() + prepare_overlay_live(state, scene.input1, t, extra_alpha_factor) + else + scene.overlay.overlay_effect:enable() + prepare_overlay_live(state, scene, t, extra_alpha_factor) end - prepare_overlay_live(chain, t, extra_alpha_factor) end - return chain.chain, prepare + return scene.scene end --- SBS code (live_signal_num == SBS_SIGNAL_NUM, or in a transition to/from it). -function get_sbs_chain(signals, t, width, height, input_resolution) - local input0_type = get_input_type(signals, INPUT0_SIGNAL_NUM) - local input1_type = get_input_type(signals, INPUT4_SIGNAL_NUM) - return sbs_chains[input0_type][overlay_enabled][input1_type][true] +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 --- Called every frame. Get the chain for displaying at input , +-- Called every frame. Get the scene for displaying at input , -- where 0 is live, 1 is preview, 2 is the first channel to display -- in the bottom bar, and so on up to num_channels()+1. t is the -- current time in seconds. width and height are the dimensions of @@ -744,52 +722,24 @@ local last_rate = 0.0 -- is basically an exposed InputState, which you can use to -- query for information about the signals at the point of the current -- frame. In particular, you can call get_width() and get_height() --- for any signal number, and use that to e.g. assist in chain selection. +-- for any signal number, and use that to e.g. assist in scene selection. -- --- You should return two objects; the chain itself, and then a --- function (taking no parameters) that is run just before rendering. --- The function needs to call connect_signal on any inputs, so that --- it gets updated video data for the given frame. (You are allowed --- to switch which input your input is getting from between frames, --- but not calling connect_signal results in undefined behavior.) --- If you want to change any parameters in the chain, this is also --- the right place. --- --- 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) +-- You should return scene to use, after having set any parameters you +-- want to set (through set_int() etc.). The parameters will be snapshot +-- at return time and used during rendering. +function get_scene(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 -- Save some CPU time if we're not having SBS on live. local new_rate - if live_signal_num == SBS_SIGNAL_NUM or - preview_signal_num == SBS_SIGNAL_NUM or - transition_type == ZOOM_TRANSITION then + if state.live_signal_num == SBS_SIGNAL_NUM or + state.preview_signal_num == SBS_SIGNAL_NUM or + state.transition_type == ZOOM_TRANSITION then new_rate = 1.0 else new_rate = 0.0001 @@ -803,44 +753,67 @@ function get_chain(num, t, width, height, signals) if num == 0 then -- Live. -- See if we're in a transition. finish_transitions(t) - if transition_type == ZOOM_TRANSITION then + if state.transition_type == ZOOM_TRANSITION then -- Transition in or out of SBS. - local chain = get_sbs_chain(signals, t, width, height, input_resolution) - prepare = function() - prepare_sbs_chain(chain, calc_zoom_progress(t), transition_type, transition_src_signal, transition_dst_signal, width, height, input_resolution) - prepare_overlay_live(chain, t, 1.0) - end - return chain.chain, prepare - elseif transition_type == NO_TRANSITION and live_signal_num == SBS_SIGNAL_NUM then + sbs_scene.input0.overlay.overlay_effect:enable_if(state.overlay_enabled) + prepare_sbs_scene(state, sbs_scene, calc_zoom_progress(t), state.transition_type, state.transition_src_signal, state.transition_dst_signal, width, height, input_resolution, true) + prepare_overlay_live(state, sbs_scene, t, 1.0) + return sbs_scene.scene + elseif state.transition_type == NO_TRANSITION and state.live_signal_num == SBS_SIGNAL_NUM then -- Static SBS view. - local chain = get_sbs_chain(signals, t, width, height, input_resolution) - prepare = function() - prepare_sbs_chain(chain, 0.0, NO_TRANSITION, 0, SBS_SIGNAL_NUM, width, height, input_resolution) - prepare_overlay_live(chain, t, 1.0) - end - return chain.chain, prepare - elseif transition_type == FADE_TRANSITION then - return get_fade_chain(signals, t, width, height, input_resolution) - elseif is_plain_signal(live_signal_num) then - local input_type = get_input_type(signals, live_signal_num) - local input_scale = needs_scale(signals, live_signal_num, width, height) - local overlay_really_enabled = overlay_enabled and simple_signal_has_overlay(live_signal_num) - local chain = simple_chains[input_type][input_scale][overlay_really_enabled][true] - prepare = function() - if input_type ~= "video" then - chain.input:connect_signal(live_signal_num) + sbs_scene.input0.overlay.overlay_effect:enable_if(state.overlay_enabled) + prepare_sbs_scene(state, sbs_scene, 0.0, NO_TRANSITION, 0, SBS_SIGNAL_NUM, width, height, input_resolution, true) + prepare_overlay_live(state, sbs_scene, t, 1.0) + return sbs_scene.scene + elseif state.transition_type == FADE_TRANSITION then + return get_fade_scene(state, signals, t, width, height, input_resolution) + elseif is_plain_signal(state.live_signal_num) then + local overlay_really_enabled = state.overlay_enabled and simple_signal_has_overlay(state.live_signal_num) + if state.stinger_in_progress then + stinger_scene.overlay.overlay_effect:enable_if(overlay_really_enabled) + if state.live_signal_num == VIDEO_SIGNAL_NUM then + stinger_scene.input:display(iptv_video) + else + stinger_scene.input:display(state.live_signal_num) end - set_neutral_color_from_signal(chain.wb_effect, live_signal_num) - set_scale_parameters_if_needed(chain, width, height) - prepare_overlay_live(chain, t, 1.0) - end - return chain.chain, prepare - elseif live_signal_num == STATIC_SIGNAL_NUM then -- Static picture. - local chain = static_chains[true] - prepare = function() - prepare_overlay_live(chain, t, 1.0) -- FIXME: Should this ever be here? + set_scale_parameters_if_needed(stinger_scene, signals, state.live_signal_num, width, height, true) + set_neutral_color_from_signal(state, stinger_scene.wb_effect, state.live_signal_num) + + stinger_scene.stinger_input:display(stinger_images[state.stinger_frame]) + 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 + + if overlay_really_enabled then + prepare_overlay_live(state, stinger_scene, t, 1.0) + end + return stinger_scene.scene + else + simple_scene.overlay.overlay_effect:enable_if(overlay_really_enabled) + if state.live_signal_num == VIDEO_SIGNAL_NUM then + simple_scene.input:display(iptv_video) + else + simple_scene.input:display(state.live_signal_num) + end + set_scale_parameters_if_needed(simple_scene, signals, state.live_signal_num, width, height, true) + set_neutral_color_from_signal(state, simple_scene.wb_effect, state.live_signal_num) + prepare_overlay_live(state, simple_scene, t, 1.0) + return simple_scene.scene end - return chain.chain, prepare + elseif state.live_signal_num == STATIC_SIGNAL_NUM then -- Static picture. + return static_scene else assert(false) end @@ -850,56 +823,43 @@ function get_chain(num, t, width, height, signals) -- The M/E preview matches what we'd put live by doing a transition, as always. local show_overlay = false if num == 1 then -- Preview. - if preview_signal_num == OVERLAY_SIGNAL_NUM then - num = live_signal_num + 2 - show_overlay = not overlay_enabled + if state.preview_signal_num == OVERLAY_SIGNAL_NUM then + num = state.live_signal_num + 2 + show_overlay = not state.overlay_enabled - if transition_type ~= NO_TRANSITION then - num = transition_dst_signal + 2 + if state.transition_type ~= NO_TRANSITION then + num = state.transition_dst_signal + 2 end else - num = preview_signal_num + 2 - show_overlay = overlay_enabled and simple_signal_has_overlay(preview_signal_num) + num = state.preview_signal_num + 2 + show_overlay = state.overlay_enabled and simple_signal_has_overlay(state.preview_signal_num) end end -- Individual preview inputs (usually without overlay). if is_plain_signal(num - 2) then local signal_num = num - 2 - local input_type = get_input_type(signals, signal_num) - local input_scale = needs_scale(signals, signal_num, width, height) - local chain = simple_chains[input_type][input_scale][show_overlay][false] - prepare = function() - if input_type ~= "video" then - chain.input:connect_signal(signal_num) - end - set_neutral_color(chain.wb_effect, neutral_colors[signal_num + 1]) - set_scale_parameters_if_needed(chain, width, height) - prepare_overlay_static(chain, t) + simple_scene.overlay.overlay_effect:enable_if(show_overlay) + if signal_num == VIDEO_SIGNAL_NUM then + simple_scene.input:display(iptv_video) + else + simple_scene.input:display(signal_num) end - return chain.chain, prepare + set_scale_parameters_if_needed(simple_scene, signals, signal_num, width, height, false) + set_neutral_color_from_signal(state, simple_scene.wb_effect, signal_num) + prepare_overlay_static(simple_scene, t) + return simple_scene.scene end if num == SBS_SIGNAL_NUM + 2 then - local input0_type = get_input_type(signals, INPUT0_SIGNAL_NUM) - local input1_type = get_input_type(signals, INPUT4_SIGNAL_NUM) - local chain = sbs_chains[input0_type][show_overlay][input1_type][false] - prepare = function() - prepare_sbs_chain(chain, 0.0, NO_TRANSITION, 0, SBS_SIGNAL_NUM, width, height, input_resolution) - end - return chain.chain, prepare + sbs_scene.input0.overlay.overlay_effect:enable_if(show_overlay) + prepare_sbs_scene(state, sbs_scene, 0.0, NO_TRANSITION, 0, SBS_SIGNAL_NUM, width, height, input_resolution, false) + return sbs_scene.scene end if num == STATIC_SIGNAL_NUM + 2 then - local chain = static_chains[false] - prepare = function() - prepare_overlay_static(chain, t) - end - return chain.chain, prepare + return static_scene end if num == OVERLAY_SIGNAL_NUM + 2 then - prepare = function() --- prepare_overlay(overlay_chain_lq, t) - end - return overlay_chain_lq, prepare + return overlay_scene_lq end end @@ -932,11 +892,11 @@ function pos_from_top_left(x, y, width, height, screen_width, screen_height) } end -function prepare_sbs_chain(chain, t, transition_type, src_signal, dst_signal, screen_width, screen_height, input_resolution) - chain.input0.input:connect_signal(0) - chain.input1.input:connect_signal(4) - set_neutral_color(chain.input0.wb_effect, neutral_colors[1]) - set_neutral_color(chain.input1.wb_effect, neutral_colors[5]) +function prepare_sbs_scene(state, scene, t, transition_type, src_signal, dst_signal, screen_width, screen_height, input_resolution, hq) + scene.input0.input:display(0) + scene.input1.input:display(4) + set_neutral_color(scene.input0.wb_effect, state.neutral_colors[1]) + set_neutral_color(scene.input1.wb_effect, state.neutral_colors[5]) -- Both inputs are the same size (true side-by-side). local pos0 = pos_from_top_left(1280 - 616 - 16, 186, 616, 347, screen_width, screen_height) @@ -968,8 +928,8 @@ function prepare_sbs_chain(chain, t, transition_type, src_signal, dst_signal, sc end -- NOTE: input_resolution is not 1-indexed, unlike usual Lua arrays. - place_rectangle_with_affine(chain.input0.resample_effect, chain.input0.resize_effect, chain.input0.padding_effect, pos0, affine_param, screen_width, screen_height, input_resolution[0].width, input_resolution[0].height) - place_rectangle_with_affine(chain.input1.resample_effect, chain.input1.resize_effect, chain.input1.padding_effect, pos1, affine_param, screen_width, screen_height, input_resolution[1].width, input_resolution[1].height) + place_rectangle_with_affine(scene.input0, pos0, affine_param, screen_width, screen_height, input_resolution[0].width, input_resolution[0].height, hq) + place_rectangle_with_affine(scene.input1, pos1, affine_param, screen_width, screen_height, input_resolution[1].width, input_resolution[1].height, hq) end -- Find the transformation that changes the first rectangle to the second one. @@ -984,40 +944,34 @@ function find_affine_param(a, b) } end -function place_rectangle_with_affine(resample_effect, resize_effect, padding_effect, pos, aff, screen_width, screen_height, input_width, input_height) +function place_rectangle_with_affine(input, pos, aff, screen_width, screen_height, input_width, input_height, hq) local x0 = pos.x0 * aff.sx + aff.tx local x1 = pos.x1 * aff.sx + aff.tx local y0 = pos.y0 * aff.sy + aff.ty local y1 = pos.y1 * aff.sy + aff.ty - place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height) + place_rectangle(input, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height, hq) end -function place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height) - local srcx0 = 0.0 - local srcx1 = 1.0 - local srcy0 = 0.0 - local srcy1 = 1.0 - - padding_effect:set_int("width", screen_width) - padding_effect:set_int("height", screen_height) +function place_rectangle(input, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height, hq) + input.padding_effect:set_int("width", screen_width) + input.padding_effect:set_int("height", screen_height) -- Cull. if x0 > screen_width or x1 < 0.0 or y0 > screen_height or y1 < 0.0 then - if resample_effect ~= nil then - resample_effect:set_int("width", 1) - resample_effect:set_int("height", 1) - resample_effect:set_float("zoom_x", screen_width) - resample_effect:set_float("zoom_y", screen_height) - else - resize_effect:set_int("width", 1) - resize_effect:set_int("height", 1) - end - padding_effect:set_int("left", screen_width + 100) - padding_effect:set_int("top", screen_height + 100) + input.resample_effect:choose(ResizeEffect) -- Low-quality resizing. + input.resample_effect:set_int("width", 1) + input.resample_effect:set_int("height", 1) + input.padding_effect:set_int("left", screen_width + 100) + input.padding_effect:set_int("top", screen_height + 100) return end + local srcx0 = 0.0 + local srcx1 = 1.0 + local srcy0 = 0.0 + local srcy1 = 1.0 + -- Clip. if x0 < 0 then srcx0 = -x0 / (x1 - x0) @@ -1036,8 +990,10 @@ function place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, y1 = screen_height end - if resample_effect ~= nil then + if hq then -- High-quality resampling. + local resample_effect = input.resample_effect:choose(ResampleEffect) + local x_subpixel_offset = x0 - math.floor(x0) local y_subpixel_offset = y0 - math.floor(y0) @@ -1045,61 +1001,69 @@ function place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, -- and then add an extra pixel so we have some leeway for the border. local width = math.ceil(x1 - x0) + 1 local height = math.ceil(y1 - y0) + 1 - resample_effect:set_int("width", width) - resample_effect:set_int("height", height) + input.resample_effect:set_int("width", width) + input.resample_effect:set_int("height", height) -- Correct the discrepancy with zoom. (This will leave a small -- excess edge of pixels and subpixels, which we'll correct for soon.) local zoom_x = (x1 - x0) / (width * (srcx1 - srcx0)) local zoom_y = (y1 - y0) / (height * (srcy1 - srcy0)) - resample_effect:set_float("zoom_x", zoom_x) + resample_effect:set_float("zoom_x", zoom_x) -- Use the actual effect specialization; specific to ResampleEffect. resample_effect:set_float("zoom_y", zoom_y) resample_effect:set_float("zoom_center_x", 0.0) resample_effect:set_float("zoom_center_y", 0.0) -- Padding must also be to a whole-pixel offset. - padding_effect:set_int("left", math.floor(x0)) - padding_effect:set_int("top", math.floor(y0)) + input.padding_effect:set_int("left", math.floor(x0)) + input.padding_effect:set_int("top", math.floor(y0)) -- Correct _that_ discrepancy by subpixel offset in the resampling. resample_effect:set_float("left", srcx0 * input_width - x_subpixel_offset / zoom_x) resample_effect:set_float("top", srcy0 * input_height - y_subpixel_offset / zoom_y) -- Finally, adjust the border so it is exactly where we want it. - padding_effect:set_float("border_offset_left", x_subpixel_offset) - padding_effect:set_float("border_offset_right", x1 - (math.floor(x0) + width)) - padding_effect:set_float("border_offset_top", y_subpixel_offset) - padding_effect:set_float("border_offset_bottom", y1 - (math.floor(y0) + height)) + input.padding_effect:set_float("border_offset_left", x_subpixel_offset) + input.padding_effect:set_float("border_offset_right", x1 - (math.floor(x0) + width)) + input.padding_effect:set_float("border_offset_top", y_subpixel_offset) + input.padding_effect:set_float("border_offset_bottom", y1 - (math.floor(y0) + height)) else -- Lower-quality simple resizing. + input.resample_effect:choose(ResizeEffect) + local width = round(x1 - x0) local height = round(y1 - y0) - resize_effect:set_int("width", width) - resize_effect:set_int("height", height) + input.resample_effect:set_int("width", width) + input.resample_effect:set_int("height", height) -- Padding must also be to a whole-pixel offset. - padding_effect:set_int("left", math.floor(x0)) - padding_effect:set_int("top", math.floor(y0)) + input.padding_effect:set_int("left", math.floor(x0)) + input.padding_effect:set_int("top", math.floor(y0)) + + -- No subpixel stuff. + input.padding_effect:set_float("border_offset_left", 0.0) + input.padding_effect:set_float("border_offset_right", 0.0) + input.padding_effect:set_float("border_offset_top", 0.0) + input.padding_effect:set_float("border_offset_bottom", 0.0) end end -function prepare_overlay_live(chain, t, extra_alpha_factor) - if chain.overlay then - local tt = calc_fade_progress(t, overlay_transition_start, overlay_transition_end) - overlay_alpha = overlay_alpha_src + tt * (overlay_alpha_dst - overlay_alpha_src) +function prepare_overlay_live(state, scene, t, extra_alpha_factor) + if scene.overlay then + local tt = calc_fade_progress(t, state.overlay_transition_start, state.overlay_transition_end) + local overlay_alpha = state.overlay_alpha_src + tt * (state.overlay_alpha_dst - state.overlay_alpha_src) overlay_alpha = overlay_alpha * extra_alpha_factor - --print("overlay_alpha=" .. overlay_alpha .. " [" .. overlay_alpha_src .. "," .. overlay_alpha_dst .. "]@" .. tt) - if t > overlay_transition_end and overlay_alpha_dst == 0.0 then - overlay_enabled = false -- Takes effect next frame. + --print("overlay_alpha=" .. overlay_alpha .. " [" .. state.overlay_alpha_src .. "," .. state.overlay_alpha_dst .. "]@" .. tt) + if t > state.overlay_transition_end and state.overlay_alpha_dst == 0.0 then + state.overlay_enabled = false -- Takes effect next frame. -- print("Turning off overlay") end - chain.overlay.multiply_effect:set_vec4("factor", overlay_alpha, overlay_alpha, overlay_alpha, overlay_alpha) + scene.overlay.multiply_effect:set_vec4("factor", overlay_alpha, overlay_alpha, overlay_alpha, overlay_alpha) end end -function prepare_overlay_static(chain) - if chain.overlay then - chain.overlay.multiply_effect:set_vec4("factor", 1.0, 1.0, 1.0, 1.0) +function prepare_overlay_static(scene) + if scene.overlay then + scene.overlay.multiply_effect:set_vec4("factor", 1.0, 1.0, 1.0, 1.0) end end @@ -1107,19 +1071,19 @@ function set_neutral_color(effect, color) effect:set_vec3("neutral_color", color[1], color[2], color[3]) end -function set_neutral_color_from_signal(effect, signal) +function set_neutral_color_from_signal(state, effect, signal) if is_plain_signal(signal) then - set_neutral_color(effect, neutral_colors[signal - INPUT0_SIGNAL_NUM + 1]) + set_neutral_color(effect, state.neutral_colors[signal - INPUT0_SIGNAL_NUM + 1]) end end function calc_zoom_progress(t) - if t < transition_start then + if t < state.transition_start then return 0.0 - elseif t > transition_end then + elseif t > state.transition_end then return 1.0 else - local tt = (t - transition_start) / (transition_end - transition_start) + local tt = (t - state.transition_start) / (state.transition_end - state.transition_start) -- Smooth it a bit. return math.sin(tt * 3.14159265358 * 0.5) end