diff --git a/src/core/producer/layer.cpp b/src/core/producer/layer.cpp index 893bd27802..5cf3024622 100644 --- a/src/core/producer/layer.cpp +++ b/src/core/producer/layer.cpp @@ -45,17 +45,23 @@ struct layer::impl void resume() { paused_ = false; } - void load(spl::shared_ptr producer, bool preview, bool auto_play) + void load(spl::shared_ptr producer, bool preview_producer, bool auto_play) { background_ = std::move(producer); auto_play_ = auto_play; if (auto_play_ && foreground_ == frame_producer::empty()) { play(); - } else if (preview) { - foreground_ = std::move(background_); - background_ = frame_producer::empty(); - paused_ = true; + } else if (preview_producer) { + preview(true); + } + } + + void preview(bool force) + { + if (force || background_ != frame_producer::empty()) { + play(); + paused_ = true; } } @@ -160,6 +166,7 @@ void layer::load(spl::shared_ptr frame_producer, bool preview, b return impl_->load(std::move(frame_producer), preview, auto_play); } void layer::play() { impl_->play(); } +void layer::preview() { impl_->preview(false); } void layer::pause() { impl_->pause(); } void layer::resume() { impl_->resume(); } void layer::stop() { impl_->stop(); } diff --git a/src/core/producer/layer.h b/src/core/producer/layer.h index 688e50fa8f..cda49ff4c9 100644 --- a/src/core/producer/layer.h +++ b/src/core/producer/layer.h @@ -45,6 +45,7 @@ class layer final void load(spl::shared_ptr producer, bool preview, bool auto_play = false); void play(); + void preview(); void pause(); void resume(); void stop(); diff --git a/src/core/producer/stage.cpp b/src/core/producer/stage.cpp index edbacb9f67..18110e509e 100644 --- a/src/core/producer/stage.cpp +++ b/src/core/producer/stage.cpp @@ -230,6 +230,11 @@ struct stage::impl : public std::enable_shared_from_this { return executor_.begin_invoke([=] { get_layer(index).load(producer, preview, auto_play); }); } + + std::future preview(int index) + { + return executor_.begin_invoke([=] { get_layer(index).preview(); }); + } std::future pause(int index) { @@ -369,6 +374,7 @@ std::future stage::load(int index, const spl::shared_ptr& { return impl_->load(index, producer, preview, auto_play); } +std::future stage::preview(int index) { return impl_->preview(index); } std::future stage::pause(int index) { return impl_->pause(index); } std::future stage::resume(int index) { return impl_->resume(index); } std::future stage::play(int index) { return impl_->play(index); } diff --git a/src/core/producer/stage.h b/src/core/producer/stage.h index 0aaca31e03..b8d276d816 100644 --- a/src/core/producer/stage.h +++ b/src/core/producer/stage.h @@ -71,6 +71,7 @@ class stage final std::future get_current_transform(int index); std::future load(int index, const spl::shared_ptr& producer, bool preview = false, bool auto_play = false); + std::future preview(int index); std::future pause(int index); std::future resume(int index); std::future play(int index); diff --git a/src/protocol/CMakeLists.txt b/src/protocol/CMakeLists.txt index e14a81ffaa..8705dc9e16 100644 --- a/src/protocol/CMakeLists.txt +++ b/src/protocol/CMakeLists.txt @@ -25,6 +25,7 @@ set(SOURCES util/lock_container.cpp util/strategy_adapters.cpp util/http_request.cpp + util/tokenize.cpp StdAfx.cpp ) @@ -62,6 +63,7 @@ set(HEADERS util/protocol_strategy.h util/strategy_adapters.h util/http_request.h + util/tokenize.h StdAfx.h ) diff --git a/src/protocol/amcp/AMCPCommand.h b/src/protocol/amcp/AMCPCommand.h index b3152cba50..3e5ea7e793 100644 --- a/src/protocol/amcp/AMCPCommand.h +++ b/src/protocol/amcp/AMCPCommand.h @@ -82,57 +82,52 @@ using amcp_command_func = std::function; class AMCPCommand { private: - command_context ctx_; - amcp_command_func command_; - int min_num_params_; - std::wstring name_; - std::wstring replyString_; - std::wstring request_id_; + command_context ctx_; + const amcp_command_func command_; + const int min_num_params_; + const std::wstring name_; + const std::wstring request_id_; public: - AMCPCommand(const command_context& ctx, + AMCPCommand(const command_context& ctx, const amcp_command_func& command, int min_num_params, - const std::wstring& name) + const std::wstring& name, + const std::wstring& request_id) : ctx_(ctx) , command_(command) , min_num_params_(min_num_params) , name_(name) + , request_id_(request_id) { } using ptr_type = std::shared_ptr; - bool Execute() + const std::wstring Execute() { - SetReplyString(command_(ctx_)); - return true; + return command_(ctx_); } int minimum_parameters() const { return min_num_params_; } - void SendReply() + void SendReply(std::wstring replyString) { - if (replyString_.empty()) + if (replyString.empty()) return; - ctx_.client->send(std::move(replyString_)); - } + if (!request_id_.empty()) + replyString = L"RES " + request_id_ + L" " + replyString; - std::vector& parameters() { return ctx_.parameters; } + ctx_.client->send(std::move(replyString)); + } - IO::ClientInfoPtr client() { return ctx_.client; } + const std::vector& parameters() const { return ctx_.parameters; } - std::wstring print() const { return name_; } + int channel_index() const { return ctx_.channel_index; } - void set_request_id(std::wstring request_id) { request_id_ = std::move(request_id); } + IO::ClientInfoPtr client() const { return ctx_.client; } - void SetReplyString(const std::wstring& str) - { - if (request_id_.empty()) - replyString_ = str; - else - replyString_ = L"RES " + request_id_ + L" " + str; - } + const std::wstring& print() const { return name_; } }; }}} // namespace caspar::protocol::amcp diff --git a/src/protocol/amcp/AMCPCommandQueue.cpp b/src/protocol/amcp/AMCPCommandQueue.cpp index 8b9553ef24..bf103dc044 100644 --- a/src/protocol/amcp/AMCPCommandQueue.cpp +++ b/src/protocol/amcp/AMCPCommandQueue.cpp @@ -71,8 +71,7 @@ void AMCPCommandQueue::AddCommand(AMCPCommand::ptr_type pCurrentCommand) try { CASPAR_LOG(error) << "AMCP Command Queue Overflow."; CASPAR_LOG(error) << "Failed to execute command:" << pCurrentCommand->print(); - pCurrentCommand->SetReplyString(L"504 QUEUE OVERFLOW\r\n"); - pCurrentCommand->SendReply(); + pCurrentCommand->SendReply(L"504 QUEUE OVERFLOW\r\n"); } catch (...) { CASPAR_LOG_CURRENT_EXCEPTION(); } @@ -89,32 +88,29 @@ void AMCPCommandQueue::AddCommand(AMCPCommand::ptr_type pCurrentCommand) CASPAR_LOG(debug) << "Executing command: " << print; - if (pCurrentCommand->Execute()) - CASPAR_LOG(debug) << "Executed command (" << timer.elapsed() << "s): " << print; - else - CASPAR_LOG(warning) << "Failed to execute command: " << print; + auto res = pCurrentCommand->Execute(); + CASPAR_LOG(debug) << "Executed command (" << timer.elapsed() << "s): " << print; + pCurrentCommand->SendReply(res); } catch (file_not_found&) { CASPAR_LOG(error) << " Turn on log level debug for stacktrace."; - pCurrentCommand->SetReplyString(L"404 " + pCurrentCommand->print() + L" FAILED\r\n"); + pCurrentCommand->SendReply(L"404 " + pCurrentCommand->print() + L" FAILED\r\n"); } catch (expected_user_error&) { - pCurrentCommand->SetReplyString(L"403 " + pCurrentCommand->print() + L" FAILED\r\n"); + pCurrentCommand->SendReply(L"403 " + pCurrentCommand->print() + L" FAILED\r\n"); } catch (user_error&) { CASPAR_LOG(error) << " Check syntax. Turn on log level debug for stacktrace."; - pCurrentCommand->SetReplyString(L"403 " + pCurrentCommand->print() + L" FAILED\r\n"); + pCurrentCommand->SendReply(L"403 " + pCurrentCommand->print() + L" FAILED\r\n"); } catch (std::out_of_range&) { CASPAR_LOG(error) << L"Missing parameter. Check syntax. Turn on log level debug for stacktrace."; - pCurrentCommand->SetReplyString(L"402 " + pCurrentCommand->print() + L" FAILED\r\n"); + pCurrentCommand->SendReply(L"402 " + pCurrentCommand->print() + L" FAILED\r\n"); } catch (boost::bad_lexical_cast&) { CASPAR_LOG(error) << L"Invalid parameter. Check syntax. Turn on log level debug for stacktrace."; - pCurrentCommand->SetReplyString(L"403 " + pCurrentCommand->print() + L" FAILED\r\n"); + pCurrentCommand->SendReply(L"403 " + pCurrentCommand->print() + L" FAILED\r\n"); } catch (...) { CASPAR_LOG_CURRENT_EXCEPTION(); CASPAR_LOG(error) << "Failed to execute command: " << pCurrentCommand->print(); - pCurrentCommand->SetReplyString(L"501 " + pCurrentCommand->print() + L" FAILED\r\n"); + pCurrentCommand->SendReply(L"501 " + pCurrentCommand->print() + L" FAILED\r\n"); } - pCurrentCommand->SendReply(); - CASPAR_LOG(trace) << "Ready for a new command"; } catch (...) { CASPAR_LOG_CURRENT_EXCEPTION(); diff --git a/src/protocol/amcp/AMCPCommandsImpl.cpp b/src/protocol/amcp/AMCPCommandsImpl.cpp index af63f637e8..ee244fbc6c 100644 --- a/src/protocol/amcp/AMCPCommandsImpl.cpp +++ b/src/protocol/amcp/AMCPCommandsImpl.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -223,31 +224,39 @@ std::wstring loadbg_command(command_context& ctx) core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1; core::diagnostics::call_context::for_thread().layer = ctx.layer_index(); - auto channel = ctx.channel.channel; - auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters); + auto channel = ctx.channel.channel; + bool auto_play = contains_param(L"AUTO", ctx.parameters); - if (pFP == frame_producer::empty()) - CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L"")); + try { + auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters); - bool auto_play = contains_param(L"AUTO", ctx.parameters); + if (pFP == frame_producer::empty()) + CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L"")); - spl::shared_ptr transition_producer = frame_producer::empty(); - transition_info transitionInfo; - sting_info stingInfo; + spl::shared_ptr transition_producer = frame_producer::empty(); + transition_info transitionInfo; + sting_info stingInfo; - if (try_match_sting(ctx.parameters, stingInfo)) { - transition_producer = create_sting_producer(get_producer_dependencies(channel, ctx), pFP, stingInfo); - } else { - std::wstring message; - for (size_t n = 0; n < ctx.parameters.size(); ++n) - message += boost::to_upper_copy(ctx.parameters[n]) + L" "; + if (try_match_sting(ctx.parameters, stingInfo)) { + transition_producer = create_sting_producer(get_producer_dependencies(channel, ctx), pFP, stingInfo); + } else { + std::wstring message; + for (size_t n = 0; n < ctx.parameters.size(); ++n) + message += boost::to_upper_copy(ctx.parameters[n]) + L" "; - // Always fallback to transition - try_match_transition(message, transitionInfo); - transition_producer = create_transition_producer(pFP, transitionInfo); - } + // Always fallback to transition + try_match_transition(message, transitionInfo); + transition_producer = create_transition_producer(pFP, transitionInfo); + } - channel->stage().load(ctx.layer_index(), transition_producer, false, auto_play); // TODO: LOOP + channel->stage().load(ctx.layer_index(), transition_producer, false, auto_play); // TODO: LOOP + } catch (file_not_found&) { + if (contains_param(L"CLEAR_ON_404", ctx.parameters)) { + channel->stage().load( + ctx.layer_index(), core::create_color_producer(channel->frame_factory(), 0), false, auto_play); + } + throw; + } return L"202 LOADBG OK\r\n"; } @@ -257,19 +266,40 @@ std::wstring load_command(command_context& ctx) core::diagnostics::scoped_call_context save; core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1; core::diagnostics::call_context::for_thread().layer = ctx.layer_index(); - auto pFP = - ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters); - auto pFP2 = create_transition_producer(pFP, transition_info{}); - ctx.channel.channel->stage().load(ctx.layer_index(), pFP2, true); + if (ctx.parameters.empty()) { + // Must be a promoting load + ctx.channel.channel->stage().preview(ctx.layer_index()); + } else { + try { + auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), + ctx.parameters); + auto pFP2 = create_transition_producer(pFP, transition_info{}); + + ctx.channel.channel->stage().load(ctx.layer_index(), pFP2, true); + } catch (file_not_found&) { + if (contains_param(L"CLEAR_ON_404", ctx.parameters)) { + ctx.channel.channel->stage().load( + ctx.layer_index(), core::create_color_producer(ctx.channel.channel->frame_factory(), 0), true); + } + throw; + } + } return L"202 LOAD OK\r\n"; } std::wstring play_command(command_context& ctx) { - if (!ctx.parameters.empty()) - loadbg_command(ctx); + try { + if (!ctx.parameters.empty()) + loadbg_command(ctx); + } catch (file_not_found&) { + if (contains_param(L"CLEAR_ON_404", ctx.parameters)) { + ctx.channel.channel->stage().play(ctx.layer_index()); + } + throw; + } ctx.channel.channel->stage().play(ctx.layer_index()); @@ -736,13 +766,14 @@ std::wstring mixer_keyer_command(command_context& ctx) transforms_applier transforms(ctx); bool value = std::stoi(ctx.parameters.at(0)); - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - transform.image_transform.is_key = value; - return transform; - }, - 0, - tweener(L"linear"))); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + transform.image_transform.is_key = value; + return transform; + }, + 0, + tweener(L"linear"))); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -755,13 +786,14 @@ std::wstring mixer_invert_command(command_context& ctx) transforms_applier transforms(ctx); bool value = std::stoi(ctx.parameters.at(0)); - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - transform.image_transform.invert = value; - return transform; - }, - 0, - tweener(L"linear"))); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + transform.image_transform.invert = value; + return transform; + }, + 0, + tweener(L"linear"))); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -826,13 +858,14 @@ std::wstring mixer_chroma_command(command_context& ctx) } } - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - transform.image_transform.chroma = chroma; - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + transform.image_transform.chroma = chroma; + return transform; + }, + duration, + tween)); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -845,13 +878,14 @@ std::wstring mixer_blend_command(command_context& ctx) transforms_applier transforms(ctx); auto value = get_blend_mode(ctx.parameters.at(0)); - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - transform.image_transform.blend_mode = value; - return transform; - }, - 0, - tweener(L"linear"))); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + transform.image_transform.blend_mode = value; + return transform; + }, + 0, + tweener(L"linear"))); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -868,13 +902,14 @@ std::wstring single_double_animatable_mixer_command(command_context& ctx, const int duration = ctx.parameters.size() > 1 ? std::stoi(ctx.parameters[1]) : 0; std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear"; - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - setter(transform, value); - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + setter(transform, value); + return transform; + }, + duration, + tween)); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -931,13 +966,14 @@ std::wstring mixer_levels_command(command_context& ctx) int duration = ctx.parameters.size() > 5 ? std::stoi(ctx.parameters[5]) : 0; std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear"; - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - transform.image_transform.levels = value; - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + transform.image_transform.levels = value; + return transform; + }, + duration, + tween)); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -961,16 +997,17 @@ std::wstring mixer_fill_command(command_context& ctx) double x_s = std::stod(ctx.parameters.at(2)); double y_s = std::stod(ctx.parameters.at(3)); - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) mutable -> frame_transform { - transform.image_transform.fill_translation[0] = x; - transform.image_transform.fill_translation[1] = y; - transform.image_transform.fill_scale[0] = x_s; - transform.image_transform.fill_scale[1] = y_s; - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) mutable -> frame_transform { + transform.image_transform.fill_translation[0] = x; + transform.image_transform.fill_translation[1] = y; + transform.image_transform.fill_scale[0] = x_s; + transform.image_transform.fill_scale[1] = y_s; + return transform; + }, + duration, + tween)); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -995,16 +1032,17 @@ std::wstring mixer_clip_command(command_context& ctx) double x_s = std::stod(ctx.parameters.at(2)); double y_s = std::stod(ctx.parameters.at(3)); - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - transform.image_transform.clip_translation[0] = x; - transform.image_transform.clip_translation[1] = y; - transform.image_transform.clip_scale[0] = x_s; - transform.image_transform.clip_scale[1] = y_s; - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + transform.image_transform.clip_translation[0] = x; + transform.image_transform.clip_translation[1] = y; + transform.image_transform.clip_scale[0] = x_s; + transform.image_transform.clip_scale[1] = y_s; + return transform; + }, + duration, + tween)); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -1024,14 +1062,15 @@ std::wstring mixer_anchor_command(command_context& ctx) double x = std::stod(ctx.parameters.at(0)); double y = std::stod(ctx.parameters.at(1)); - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) mutable -> frame_transform { - transform.image_transform.anchor[0] = x; - transform.image_transform.anchor[1] = y; - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) mutable -> frame_transform { + transform.image_transform.anchor[0] = x; + transform.image_transform.anchor[1] = y; + return transform; + }, + duration, + tween)); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -1053,16 +1092,17 @@ std::wstring mixer_crop_command(command_context& ctx) double lr_x = std::stod(ctx.parameters.at(2)); double lr_y = std::stod(ctx.parameters.at(3)); - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - transform.image_transform.crop.ul[0] = ul_x; - transform.image_transform.crop.ul[1] = ul_y; - transform.image_transform.crop.lr[0] = lr_x; - transform.image_transform.crop.lr[1] = lr_y; - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + transform.image_transform.crop.ul[0] = ul_x; + transform.image_transform.crop.ul[1] = ul_y; + transform.image_transform.crop.lr[0] = lr_x; + transform.image_transform.crop.lr[1] = lr_y; + return transform; + }, + duration, + tween)); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -1100,20 +1140,21 @@ std::wstring mixer_perspective_command(command_context& ctx) double ll_x = std::stod(ctx.parameters.at(6)); double ll_y = std::stod(ctx.parameters.at(7)); - transforms.add(stage::transform_tuple_t(ctx.layer_index(), - [=](frame_transform transform) -> frame_transform { - transform.image_transform.perspective.ul[0] = ul_x; - transform.image_transform.perspective.ul[1] = ul_y; - transform.image_transform.perspective.ur[0] = ur_x; - transform.image_transform.perspective.ur[1] = ur_y; - transform.image_transform.perspective.lr[0] = lr_x; - transform.image_transform.perspective.lr[1] = lr_y; - transform.image_transform.perspective.ll[0] = ll_x; - transform.image_transform.perspective.ll[1] = ll_y; - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + ctx.layer_index(), + [=](frame_transform transform) -> frame_transform { + transform.image_transform.perspective.ul[0] = ul_x; + transform.image_transform.perspective.ul[1] = ul_y; + transform.image_transform.perspective.ur[0] = ur_x; + transform.image_transform.perspective.ur[1] = ur_y; + transform.image_transform.perspective.lr[0] = lr_x; + transform.image_transform.perspective.lr[1] = lr_y; + transform.image_transform.perspective.ll[0] = ll_x; + transform.image_transform.perspective.ll[1] = ll_y; + return transform; + }, + duration, + tween)); transforms.apply(); return L"202 MIXER OK\r\n"; @@ -1150,20 +1191,21 @@ std::wstring mixer_grid_command(command_context& ctx) for (int x = 0; x < n; ++x) { for (int y = 0; y < n; ++y) { int index = x + y * n + 1; - transforms.add(stage::transform_tuple_t(index, - [=](frame_transform transform) -> frame_transform { - transform.image_transform.fill_translation[0] = x * delta; - transform.image_transform.fill_translation[1] = y * delta; - transform.image_transform.fill_scale[0] = delta; - transform.image_transform.fill_scale[1] = delta; - transform.image_transform.clip_translation[0] = x * delta; - transform.image_transform.clip_translation[1] = y * delta; - transform.image_transform.clip_scale[0] = delta; - transform.image_transform.clip_scale[1] = delta; - return transform; - }, - duration, - tween)); + transforms.add(stage::transform_tuple_t( + index, + [=](frame_transform transform) -> frame_transform { + transform.image_transform.fill_translation[0] = x * delta; + transform.image_transform.fill_translation[1] = y * delta; + transform.image_transform.fill_scale[0] = delta; + transform.image_transform.fill_scale[1] = delta; + transform.image_transform.clip_translation[0] = x * delta; + transform.image_transform.clip_translation[1] = y * delta; + transform.image_transform.clip_scale[0] = delta; + transform.image_transform.clip_scale[1] = delta; + return transform; + }, + duration, + tween)); } } transforms.apply(); @@ -1483,7 +1525,7 @@ std::wstring gl_gc_command(command_context& ctx) void register_commands(amcp_command_repository& repo) { repo.register_channel_command(L"Basic Commands", L"LOADBG", loadbg_command, 1); - repo.register_channel_command(L"Basic Commands", L"LOAD", load_command, 1); + repo.register_channel_command(L"Basic Commands", L"LOAD", load_command, 0); repo.register_channel_command(L"Basic Commands", L"PLAY", play_command, 0); repo.register_channel_command(L"Basic Commands", L"PAUSE", pause_command, 0); repo.register_channel_command(L"Basic Commands", L"RESUME", resume_command, 0); diff --git a/src/protocol/amcp/AMCPProtocolStrategy.cpp b/src/protocol/amcp/AMCPProtocolStrategy.cpp index 4bc67e0034..e1bd5bfaec 100644 --- a/src/protocol/amcp/AMCPProtocolStrategy.cpp +++ b/src/protocol/amcp/AMCPProtocolStrategy.cpp @@ -27,6 +27,8 @@ #include "amcp_command_repository.h" #include "amcp_shared.h" +#include "../util/tokenize.h" + #include #include @@ -98,7 +100,7 @@ struct AMCPProtocolStrategy::impl void Parse(const std::wstring& message, ClientInfoPtr client) { std::list tokens; - tokenize(message, tokens); + IO::tokenize(message, tokens); if (!tokens.empty() && boost::iequals(tokens.front(), L"PING")) { tokens.pop_front(); @@ -115,32 +117,27 @@ struct AMCPProtocolStrategy::impl CASPAR_LOG(info) << L"Received message from " << client->address() << ": " << message << L"\\r\\n"; - command_interpreter_result result; - if (interpret_command_string(tokens, result, client)) { - if (result.lock && !result.lock->check_access(client)) - result.error = error_state::access_error; - else - result.queue->AddCommand(result.command); - } - - if (result.error != error_state::no_error) { + std::wstring request_id; + std::wstring command_name; + error_state err = parse_command_string(client, tokens, request_id, command_name); + if (err!= error_state::no_error) { std::wstringstream answer; - if (!result.request_id.empty()) - answer << L"RES " << result.request_id << L" "; + if (!request_id.empty()) + answer << L"RES " << request_id << L" "; - switch (result.error) { + switch (err) { case error_state::command_error: answer << L"400 ERROR\r\n" << message << "\r\n"; break; case error_state::channel_error: - answer << L"401 " << result.command_name << " ERROR\r\n"; + answer << L"401 " << command_name << " ERROR\r\n"; break; case error_state::parameters_error: - answer << L"402 " << result.command_name << " ERROR\r\n"; + answer << L"402 " << command_name << " ERROR\r\n"; break; case error_state::access_error: - answer << L"503 " << result.command_name << " FAILED\r\n"; + answer << L"503 " << command_name << " FAILED\r\n"; break; case error_state::unknown_error: answer << L"500 FAILED\r\n"; @@ -148,180 +145,72 @@ struct AMCPProtocolStrategy::impl default: CASPAR_THROW_EXCEPTION(programming_error() << msg_info(L"Unhandled error_state enum constant " + - std::to_wstring(static_cast(result.error)))); + std::to_wstring(static_cast(err)))); } client->send(answer.str()); } } private: - bool - interpret_command_string(std::list tokens, command_interpreter_result& result, ClientInfoPtr client) + error_state parse_command_string(ClientInfoPtr client, + std::list tokens, + std::wstring& request_id, + std::wstring& command_name) { try { // Discard GetSwitch if (!tokens.empty() && tokens.front().at(0) == L'/') tokens.pop_front(); - if (!tokens.empty() && boost::iequals(tokens.front(), L"REQ")) { - tokens.pop_front(); - - if (tokens.empty()) { - result.error = error_state::parameters_error; - return false; - } - - result.request_id = tokens.front(); - tokens.pop_front(); + error_state error = parse_request_token(tokens, request_id); + if (error != error_state::no_error) { + return error; } // Fail if no more tokens. if (tokens.empty()) { - result.error = error_state::command_error; - return false; + return error_state::command_error; } - // Consume command name - result.command_name = boost::to_upper_copy(tokens.front()); - tokens.pop_front(); - - // Determine whether the next parameter is a channel spec or not - int channel_index = -1; - int layer_index = -1; - std::wstring channel_spec; - - if (!tokens.empty()) { - channel_spec = tokens.front(); - std::wstring channelid_str = boost::trim_copy(channel_spec); - std::vector split; - boost::split(split, channelid_str, boost::is_any_of("-")); - - // Use non_throwing lexical cast to not hit exception break point all the time. - if (try_lexical_cast(split[0], channel_index)) { - --channel_index; - - if (split.size() > 1) - try_lexical_cast(split[1], layer_index); - - // Consume channel-spec - tokens.pop_front(); - } + command_name = boost::to_upper_copy(tokens.front()); + const std::shared_ptr command = repo_->parse_command(client, tokens, request_id); + if (!command) { + return error_state::command_error; } - bool is_channel_command = channel_index != -1; - - // Create command instance - if (is_channel_command) { - result.command = - repo_->create_channel_command(result.command_name, client, channel_index, layer_index, tokens); - - if (result.command) { - result.lock = repo_->channels().at(channel_index).lock; - result.queue = commandQueues_.at(channel_index + 1); - } else // Might be a non channel command, although the first argument is numeric - { - // Restore backed up channel spec string. - tokens.push_front(channel_spec); - result.command = repo_->create_command(result.command_name, client, tokens); - - if (result.command) - result.queue = commandQueues_.at(0); - } - } else { - result.command = repo_->create_command(result.command_name, client, tokens); - - if (result.command) - result.queue = commandQueues_.at(0); + const int channel_index = command->channel_index(); + if (!repo_->check_channel_lock(client, channel_index)) { + return error_state::access_error; } - if (!result.command) - result.error = error_state::command_error; - else { - std::vector parameters(tokens.begin(), tokens.end()); + commandQueues_.at(channel_index + 1)->AddCommand(std::move(command)); + return error_state::no_error; - result.command->parameters() = std::move(parameters); - - if (result.command->parameters().size() < result.command->minimum_parameters()) - result.error = error_state::parameters_error; - } - - if (result.command) - result.command->set_request_id(result.request_id); } catch (std::out_of_range&) { CASPAR_LOG(error) << "Invalid channel specified."; - result.error = error_state::channel_error; + return error_state::channel_error; } catch (...) { CASPAR_LOG_CURRENT_EXCEPTION(); - result.error = error_state::unknown_error; + return error_state::unknown_error; } - - return result.error == error_state::no_error; } - template - std::size_t tokenize(const std::wstring& message, C& pTokenVector) + static error_state parse_request_token(std::list& tokens, std::wstring& request_id) { - // split on whitespace but keep strings within quotationmarks - // treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the - // string - - std::wstring currentToken; - - bool inQuote = false; - bool getSpecialCode = false; - - for (unsigned int charIndex = 0; charIndex < message.size(); ++charIndex) { - if (getSpecialCode) { - // insert code-handling here - switch (message[charIndex]) { - case L'\\': - currentToken += L"\\"; - break; - case L'\"': - currentToken += L"\""; - break; - case L'n': - currentToken += L"\n"; - break; - default: - break; - } - getSpecialCode = false; - continue; - } - - if (message[charIndex] == L'\\') { - getSpecialCode = true; - continue; - } - - if (message[charIndex] == L' ' && inQuote == false) { - if (!currentToken.empty()) { - pTokenVector.push_back(currentToken); - currentToken.clear(); - } - continue; - } - - if (message[charIndex] == L'\"') { - inQuote = !inQuote; + if (tokens.empty() || !boost::iequals(tokens.front(), L"REQ")) { + return error_state::no_error; + } - if (!currentToken.empty() || !inQuote) { - pTokenVector.push_back(currentToken); - currentToken.clear(); - } - continue; - } + tokens.pop_front(); - currentToken += message[charIndex]; + if (tokens.empty()) { + return error_state::parameters_error; } - if (!currentToken.empty()) { - pTokenVector.push_back(currentToken); - currentToken.clear(); - } + request_id = std::move(tokens.front()); + tokens.pop_front(); - return pTokenVector.size(); + return error_state::no_error; } }; diff --git a/src/protocol/amcp/amcp_command_repository.cpp b/src/protocol/amcp/amcp_command_repository.cpp index ea73212e0b..f6f887ce5a 100644 --- a/src/protocol/amcp/amcp_command_repository.cpp +++ b/src/protocol/amcp/amcp_command_repository.cpp @@ -32,8 +32,9 @@ namespace caspar { namespace protocol { namespace amcp { AMCPCommand::ptr_type find_command(const std::map>& commands, - const std::wstring& str, - const command_context& ctx, + const std::wstring& name, + const std::wstring& request_id, + command_context& ctx, std::list& tokens) { std::wstring subcommand; @@ -43,25 +44,62 @@ AMCPCommand::ptr_type find_command(const std::map(ctx, subcmd->second.first, subcmd->second.second, s); + ctx.parameters = std::move(std::vector(tokens.begin(), tokens.end())); + return std::make_shared( + ctx, subcmd->second.first, subcmd->second.second, fullname, request_id); } } // Resort to ordinary command - auto s = str; - auto command = commands.find(s); - - if (command != commands.end()) - return std::make_shared(ctx, command->second.first, command->second.second, s); + auto command = commands.find(name); + if (command != commands.end()) { + ctx.parameters = std::move(std::vector(tokens.begin(), tokens.end())); + return std::make_shared(ctx, command->second.first, command->second.second, name, request_id); + } return nullptr; } +template +bool try_lexical_cast(const In& input, Out& result) +{ + Out saved = result; + const bool success = boost::conversion::detail::try_lexical_convert(input, result); + + if (!success) + result = saved; // Needed because of how try_lexical_convert is implemented. + + return success; +} + +static void +parse_channel_id(std::list& tokens, std::wstring& channel_spec, int& channel_index, int& layer_index) +{ + if (!tokens.empty()) { + channel_spec = tokens.front(); + std::wstring channelid_str = boost::trim_copy(channel_spec); + std::vector split; + boost::split(split, channelid_str, boost::is_any_of("-")); + + // Use non_throwing lexical cast to not hit exception break point all the time. + if (try_lexical_cast(split[0], channel_index)) { + --channel_index; + + if (split.size() > 1) + try_lexical_cast(split[1], layer_index); + + // Consume channel-spec + tokens.pop_front(); + } + } +} + + struct amcp_command_repository::impl { std::vector channels; @@ -98,6 +136,100 @@ struct amcp_command_repository::impl ++index; } } + + + AMCPCommand::ptr_type create_command(const std::wstring& name, + const std::wstring& request_id, + IO::ClientInfoPtr client, + std::list& tokens) const + { + command_context ctx(std::move(client), + channel_context(), + -1, + -1, + channels, + cg_registry, + producer_registry, + consumer_registry, + shutdown_server_now, + proxy_host, + proxy_port, + ogl_device); + + return find_command(commands, name, request_id, ctx, tokens); + } + + AMCPCommand::ptr_type create_channel_command(const std::wstring& name, + const std::wstring& request_id, + IO::ClientInfoPtr client, + unsigned int channel_index, + int layer_index, + std::list& tokens) const + { + auto channel = channels.at(channel_index); + + command_context ctx(std::move(client), + channel, + channel_index, + layer_index, + channels, + cg_registry, + producer_registry, + consumer_registry, + shutdown_server_now, + proxy_host, + proxy_port, + ogl_device); + + return find_command(channel_commands, name, request_id, ctx, tokens); + } + + std::shared_ptr + parse_command(IO::ClientInfoPtr client, std::list tokens, const std::wstring& request_id) const + { + // Consume command name + const std::basic_string command_name = boost::to_upper_copy(tokens.front()); + tokens.pop_front(); + + // Determine whether the next parameter is a channel spec or not + int channel_index = -1; + int layer_index = -1; + std::wstring channel_spec; + parse_channel_id(tokens, channel_spec, channel_index, layer_index); + + // Create command instance + std::shared_ptr command; + if (channel_index >= 0) { + command = create_channel_command(command_name, request_id, client, channel_index, layer_index, tokens); + + if (!command) // Might be a non channel command, although the first argument is numeric + { + // Restore backed up channel spec string. + tokens.push_front(channel_spec); + } + } + + // Create global instance + if (!command) { + command = create_command(command_name, request_id, client, tokens); + } + + if (command && command->parameters().size() < command->minimum_parameters()) { + CASPAR_LOG(error) << "Not enough parameters in command: " << command_name; + return nullptr; + } + + return std::move(command); + } + + bool check_channel_lock(IO::ClientInfoPtr client, int channel_index) const + { + if (channel_index < 0) + return true; + + auto lock = channels.at(channel_index).lock; + return !(lock && !lock->check_access(client)); + } }; amcp_command_repository::amcp_command_repository( @@ -115,64 +247,36 @@ void amcp_command_repository::init(const std::vectorinit(channels); } -AMCPCommand::ptr_type amcp_command_repository::create_command(const std::wstring& s, +AMCPCommand::ptr_type amcp_command_repository::create_command(const std::wstring& name, + const std::wstring& request_id, IO::ClientInfoPtr client, std::list& tokens) const { - auto& self = *impl_; - - command_context ctx(std::move(client), - channel_context(), - -1, - -1, - self.channels, - self.cg_registry, - self.producer_registry, - self.consumer_registry, - self.shutdown_server_now, - self.proxy_host, - self.proxy_port, - self.ogl_device); - - auto command = find_command(self.commands, s, ctx, tokens); - - if (command) - return command; - - return nullptr; + return impl_->create_command(name, request_id, client, tokens); } const std::vector& amcp_command_repository::channels() const { return impl_->channels; } -AMCPCommand::ptr_type amcp_command_repository::create_channel_command(const std::wstring& s, +std::shared_ptr amcp_command_repository::parse_command(IO::ClientInfoPtr client, + std::list tokens, + const std::wstring& request_id) const +{ + return impl_->parse_command(client, tokens, request_id); +} + +bool amcp_command_repository::check_channel_lock(IO::ClientInfoPtr client, int channel_index) const +{ + return impl_->check_channel_lock(client, channel_index); +} + +AMCPCommand::ptr_type amcp_command_repository::create_channel_command(const std::wstring& name, + const std::wstring& request_id, IO::ClientInfoPtr client, unsigned int channel_index, int layer_index, std::list& tokens) const { - auto& self = *impl_; - - auto channel = self.channels.at(channel_index); - - command_context ctx(std::move(client), - channel, - channel_index, - layer_index, - self.channels, - self.cg_registry, - self.producer_registry, - self.consumer_registry, - self.shutdown_server_now, - self.proxy_host, - self.proxy_port, - self.ogl_device); - - auto command = find_command(self.channel_commands, s, ctx, tokens); - - if (command) - return command; - - return nullptr; + return impl_->create_channel_command(name, request_id, client, channel_index, layer_index, tokens); } void amcp_command_repository::register_command(std::wstring category, @@ -180,8 +284,7 @@ void amcp_command_repository::register_command(std::wstring category, amcp_command_func command, int min_num_params) { - auto& self = *impl_; - self.commands.insert(std::make_pair(std::move(name), std::make_pair(std::move(command), min_num_params))); + impl_->commands.insert(std::make_pair(std::move(name), std::make_pair(std::move(command), min_num_params))); } void amcp_command_repository::register_channel_command(std::wstring category, @@ -189,8 +292,7 @@ void amcp_command_repository::register_channel_command(std::wstring categor amcp_command_func command, int min_num_params) { - auto& self = *impl_; - self.channel_commands.insert(std::make_pair(std::move(name), std::make_pair(std::move(command), min_num_params))); + impl_->channel_commands.insert(std::make_pair(std::move(name), std::make_pair(std::move(command), min_num_params))); } }}} // namespace caspar::protocol::amcp diff --git a/src/protocol/amcp/amcp_command_repository.h b/src/protocol/amcp/amcp_command_repository.h index 9b6f049f0b..a7e00c1e43 100644 --- a/src/protocol/amcp/amcp_command_repository.h +++ b/src/protocol/amcp/amcp_command_repository.h @@ -43,14 +43,21 @@ class amcp_command_repository void init(const std::vector>& channels); - AMCPCommand::ptr_type - create_command(const std::wstring& s, IO::ClientInfoPtr client, std::list& tokens) const; - AMCPCommand::ptr_type create_channel_command(const std::wstring& s, + AMCPCommand::ptr_type create_command(const std::wstring& name, + const std::wstring& request_id, + IO::ClientInfoPtr client, + std::list& tokens) const; + AMCPCommand::ptr_type create_channel_command(const std::wstring& name, + const std::wstring& request_id, IO::ClientInfoPtr client, unsigned int channel_index, int layer_index, std::list& tokens) const; + std::shared_ptr + parse_command(IO::ClientInfoPtr client, std::list tokens, const std::wstring& request_id) const; + bool check_channel_lock(IO::ClientInfoPtr client, int channel_index) const; + const std::vector& channels() const; void register_command(std::wstring category, std::wstring name, amcp_command_func command, int min_num_params); diff --git a/src/protocol/util/tokenize.cpp b/src/protocol/util/tokenize.cpp new file mode 100644 index 0000000000..0a9f08401a --- /dev/null +++ b/src/protocol/util/tokenize.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2011 Sveriges Television AB + * + * This file is part of CasparCG (www.casparcg.com). + * + * CasparCG is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CasparCG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CasparCG. If not, see . + */ + +#include "tokenize.h" + +namespace caspar { namespace IO { + +std::size_t tokenize(const std::wstring& message, std::list& pTokenVector) +{ + // split on whitespace but keep strings within quotationmarks + // treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the + // string + + std::wstring currentToken; + + bool inQuote = false; + bool getSpecialCode = false; + + for (unsigned int charIndex = 0; charIndex < message.size(); ++charIndex) { + if (getSpecialCode) { + // insert code-handling here + switch (message[charIndex]) { + case L'\\': + currentToken += L"\\"; + break; + case L'\"': + currentToken += L"\""; + break; + case L'n': + currentToken += L"\n"; + break; + default: + break; + }; + getSpecialCode = false; + continue; + } + + if (message[charIndex] == L'\\') { + getSpecialCode = true; + continue; + } + + if (message[charIndex] == L' ' && inQuote == false) { + if (!currentToken.empty()) { + pTokenVector.push_back(currentToken); + currentToken.clear(); + } + continue; + } + + if (message[charIndex] == L'\"') { + inQuote = !inQuote; + + if (!currentToken.empty() || !inQuote) { + pTokenVector.push_back(currentToken); + currentToken.clear(); + } + continue; + } + + currentToken += message[charIndex]; + } + + if (!currentToken.empty()) { + pTokenVector.push_back(currentToken); + currentToken.clear(); + } + + return pTokenVector.size(); +} + +}} // namespace caspar::IO diff --git a/src/protocol/util/tokenize.h b/src/protocol/util/tokenize.h new file mode 100644 index 0000000000..c28e1c4831 --- /dev/null +++ b/src/protocol/util/tokenize.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Sveriges Television AB + * + * This file is part of CasparCG (www.casparcg.com). + * + * CasparCG is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CasparCG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CasparCG. If not, see .* + */ + +#pragma once + +#include +#include + +namespace caspar { namespace IO { + +std::size_t tokenize(const std::wstring& message, std::list& pTokenVector); + +}} // namespace caspar::IO diff --git a/src/shell/casparcg.config b/src/shell/casparcg.config index a17c9ce9bb..b6b4036b62 100644 --- a/src/shell/casparcg.config +++ b/src/shell/casparcg.config @@ -112,6 +112,10 @@ [most ffmpeg arguments related to filtering and output codecs] + + AMB LOOP + DECKLINK DEVICE 2 + diff --git a/src/shell/server.cpp b/src/shell/server.cpp index 89adb854ab..ff32515670 100644 --- a/src/shell/server.cpp +++ b/src/shell/server.cpp @@ -50,9 +50,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -133,9 +135,15 @@ struct server::impl void start() { - setup_channels(env::properties()); + auto xml_channels = setup_channels(env::properties()); CASPAR_LOG(info) << L"Initialized channels."; + setup_amcp_command_repo(); + CASPAR_LOG(info) << L"Initialized command repository."; + + setup_channel_producers(xml_channels); + CASPAR_LOG(info) << L"Initialized startup producers."; + setup_controllers(env::properties()); CASPAR_LOG(info) << L"Initialized controllers."; @@ -162,7 +170,7 @@ struct server::impl core::diagnostics::osd::shutdown(); } - void setup_channels(const boost::property_tree::wptree& pt) + std::vector setup_channels(const boost::property_tree::wptree& pt) { using boost::property_tree::wptree; @@ -214,6 +222,44 @@ struct server::impl } } } + + return xml_channels; + } + + void setup_channel_producers(const std::vector& xml_channels) + { + auto console_client = spl::make_shared(); + + for (auto& channel : channels_) { + core::diagnostics::scoped_call_context save; + core::diagnostics::call_context::for_thread().video_channel = channel->index(); + + auto xml_channel = xml_channels.at(channel->index() - 1); + + if (xml_channel.get_child_optional(L"producers")) { + for (auto& xml_producer : xml_channel | witerate_children(L"producers") | welement_context_iteration) { + ptree_verify_element_name(xml_producer, L"producer"); + + const std::wstring command = xml_producer.second.get_value(L""); + const auto attrs = xml_producer.second.get_child(L""); + const int id = attrs.get(L"id", -1); + + try { + std::list tokens{L"PLAY", + (boost::wformat(L"%i-%i") % channel->index() % id).str()}; + IO::tokenize(command, tokens); + auto cmd = amcp_command_repo_->parse_command(console_client, tokens, L""); + if (cmd) { + cmd->SendReply(cmd->Execute()); + } + } catch (const user_error&) { + CASPAR_LOG(error) << "Failed to parse command: " << command; + } catch (...) { + CASPAR_LOG_CURRENT_EXCEPTION(); + } + } + } + } } void setup_osc(const boost::property_tree::wptree& pt) @@ -254,10 +300,14 @@ struct server::impl }); } - void setup_controllers(const boost::property_tree::wptree& pt) + void setup_amcp_command_repo() { amcp_command_repo_->init(channels_); amcp::register_commands(*amcp_command_repo_); + } + + void setup_controllers(const boost::property_tree::wptree& pt) + { using boost::property_tree::wptree; for (auto& xml_controller : pt | witerate_children(L"configuration.controllers") | welement_context_iteration) {