X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=protocol%2Famcp%2FAMCPCommandsImpl.cpp;h=0fe0d942b0b7d064c79940fecc5e2a94a7245f23;hb=48e6b878823d7a118df54b91ee8d6bd7ce84c8e8;hp=7f2b1263d3c32d67c05ae0689ff5b477a240acbc;hpb=1f2344fe8705342b0503af4609064267e9ae42f4;p=casparcg diff --git a/protocol/amcp/AMCPCommandsImpl.cpp b/protocol/amcp/AMCPCommandsImpl.cpp index 7f2b1263d..0fe0d942b 100644 --- a/protocol/amcp/AMCPCommandsImpl.cpp +++ b/protocol/amcp/AMCPCommandsImpl.cpp @@ -26,46 +26,46 @@ #endif #include "AMCPCommandsImpl.h" -#include "AMCPProtocolStrategy.h" + +#include "amcp_command_repository.h" +#include "AMCPCommandQueue.h" #include #include #include -#include -#include -#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include #include #include +#include #include +#include #include #include #include #include #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include #include #include -#include #include #include @@ -87,16 +87,16 @@ /* Return codes 102 [action] Information that [action] has happened -101 [action] Information that [action] has happened plus one row of data +101 [action] Information that [action] has happened plus one row of data 202 [command] OK [command] has been executed -201 [command] OK [command] has been executed, plus one row of data +201 [command] OK [command] has been executed, plus one row of data 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line 400 ERROR the command could not be understood 401 [command] ERROR invalid/missing channel 402 [command] ERROR parameter missing -403 [command] ERROR invalid parameter +403 [command] ERROR invalid parameter 404 [command] ERROR file not found 500 FAILED internal error @@ -107,11 +107,11 @@ 600 [command] FAILED [command] not implemented */ -namespace caspar { namespace protocol { +namespace caspar { namespace protocol { namespace amcp { using namespace core; -std::wstring read_file_base64(const boost::filesystem::wpath& file) +std::wstring read_file_base64(const boost::filesystem::path& file) { using namespace boost::archive::iterators; @@ -129,12 +129,12 @@ std::wstring read_file_base64(const boost::filesystem::wpath& file) return std::wstring(result.begin(), result.end()); } -std::wstring read_utf8_file(const boost::filesystem::wpath& file) +std::wstring read_utf8_file(const boost::filesystem::path& file) { std::wstringstream result; boost::filesystem::wifstream filestream(file); - if (filestream) + if (filestream) { // Consume BOM first filestream.get(); @@ -145,7 +145,7 @@ std::wstring read_utf8_file(const boost::filesystem::wpath& file) return result.str(); } -std::wstring read_latin1_file(const boost::filesystem::wpath& file) +std::wstring read_latin1_file(const boost::filesystem::path& file) { boost::locale::generator gen; gen.locale_cache_enabled(true); @@ -165,17 +165,15 @@ std::wstring read_latin1_file(const boost::filesystem::wpath& file) std::wstring widened_result; // The first 255 codepoints in unicode is the same as in latin1 - auto from_signed_to_signed = std::function( - [] (char c) { return static_cast(c); } - ); boost::copy( - result | boost::adaptors::transformed(from_signed_to_signed), + result | boost::adaptors::transformed( + [](char c) { return static_cast(c); }), std::back_inserter(widened_result)); return widened_result; } -std::wstring read_file(const boost::filesystem::wpath& file) +std::wstring read_file(const boost::filesystem::path& file) { static const uint8_t BOM[] = {0xef, 0xbb, 0xbf}; @@ -199,66 +197,60 @@ std::wstring read_file(const boost::filesystem::wpath& file) return read_latin1_file(file); } -std::wstring MediaInfo(const boost::filesystem::path& path) +std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr& media_info_repo) { - if(boost::filesystem::is_regular_file(path)) - { - std::wstring clipttype = TEXT("N/A"); - std::wstring extension = boost::to_upper_copy(path.extension().wstring()); - if(extension == TEXT(".TGA") || extension == TEXT(".COL") || extension == L".PNG" || extension == L".JPEG" || extension == L".JPG" || - extension == L"GIF" || extension == L"BMP") - clipttype = TEXT("STILL"); - else if(extension == TEXT(".WAV") || extension == TEXT(".MP3")) - clipttype = TEXT("AUDIO"); - else if(extension == TEXT("SWF") || extension == TEXT("CT") || extension == TEXT("DV") || extension == TEXT("MOV") || extension == TEXT("MPG") || extension == TEXT("AVI") || caspar::ffmpeg::is_valid_file(path.wstring())) - clipttype = TEXT("MOVIE"); + if (!boost::filesystem::is_regular_file(path)) + return L""; - if(clipttype != TEXT("N/A")) - { - auto is_not_digit = [](char c){ return std::isdigit(c) == 0; }; + auto media_info = media_info_repo->get(path.wstring()); - auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size()-1, path.wstring().size())); + if (!media_info) + return L""; - auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path))); - writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end()); - auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end()); + auto is_not_digit = [](char c){ return std::isdigit(c) == 0; }; - auto sizeStr = boost::lexical_cast(boost::filesystem::file_size(path)); - sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end()); - auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end()); - - auto str = relativePath.replace_extension(TEXT("")).native(); - while(str.size() > 0 && (str[0] == '\\' || str[0] == '/')) - str = std::wstring(str.begin() + 1, str.end()); + auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path))); + writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end()); + auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end()); - return std::wstring() + TEXT("\"") + str + - + TEXT("\" ") + clipttype + - + TEXT(" ") + sizeStr + - + TEXT(" ") + writeTimeWStr + - + TEXT("\r\n"); - } - } - return L""; + auto sizeStr = boost::lexical_cast(boost::filesystem::file_size(path)); + sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end()); + auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end()); + + auto relativePath = get_relative_without_extension(path, env::media_folder()); + auto str = relativePath.generic_wstring(); + + if (str[0] == '\\' || str[0] == '/') + str = std::wstring(str.begin() + 1, str.end()); + + return std::wstring() + + L"\"" + str + + + L"\" " + media_info->clip_type + + + L" " + sizeStr + + + L" " + writeTimeWStr + + + L" " + boost::lexical_cast(media_info->duration) + + + L" " + boost::lexical_cast(media_info->time_base.numerator()) + L"/" + boost::lexical_cast(media_info->time_base.denominator()) + + L"\r\n"; } -std::wstring ListMedia() -{ +std::wstring ListMedia(const spl::shared_ptr& media_info_repo) +{ std::wstringstream replyString; - for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr) - replyString << MediaInfo(itr->path()); - + for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr) + replyString << MediaInfo(itr->path(), media_info_repo); + return boost::to_upper_copy(replyString.str()); } -std::wstring ListTemplates() +std::wstring ListTemplates(const spl::shared_ptr& cg_registry) { std::wstringstream replyString; for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr) - { - if(boost::filesystem::is_regular_file(itr->path()) && (itr->path().extension() == L".ft" || itr->path().extension() == L".ct")) + { + if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring())) { - auto relativePath = boost::filesystem::wpath(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size())); + auto relativePath = get_relative_without_extension(itr->path(), env::template_folder()); auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path()))); writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end()); @@ -269,1096 +261,602 @@ std::wstring ListTemplates() auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end()); - std::wstring dir = relativePath.parent_path().native(); - std::wstring file = boost::to_upper_copy(relativePath.filename().wstring()); - relativePath = boost::filesystem::wpath(dir + L"/" + file); - - auto str = relativePath.replace_extension(TEXT("")).native(); + auto dir = relativePath.parent_path(); + auto file = boost::to_upper_copy(relativePath.filename().wstring()); + relativePath = dir / file; + + auto str = relativePath.generic_wstring(); boost::trim_if(str, boost::is_any_of("\\/")); - replyString << TEXT("\"") << str - << TEXT("\" ") << sizeWStr - << TEXT(" ") << writeTimeWStr - << TEXT("\r\n"); + auto template_type = cg_registry->get_cg_producer_name(str); + + replyString << L"\"" << str + << L"\" " << sizeWStr + << L" " << writeTimeWStr + << L" " << template_type + << L"\r\n"; } } return replyString.str(); } -namespace amcp { - -void AMCPCommand::SendReply() +std::vector> get_channels(const command_context& ctx) { - if(replyString_.empty()) - return; - - client_->send(std::move(replyString_)); + return cpplinq::from(ctx.channels) + .select([](channel_context c) { return spl::make_shared_ptr(c.channel); }) + .to_vector(); } -bool DiagnosticsCommand::DoExecute() -{ - try - { - diagnostics::show_graphs(true); +core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr& channel, const command_context& ctx) +{ + return core::frame_producer_dependencies( + channel->frame_factory(), + get_channels(ctx), + channel->video_format_desc(), + ctx.producer_registry); +} - SetReplyString(TEXT("202 DIAG OK\r\n")); +// Basic Commands - return true; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 DIAG FAILED\r\n")); - return false; - } +void loadbg_describer(core::help_sink& sink, const core::help_repository& repository) +{ + sink.short_description(L"Load a media file or resource in the background."); + sink.syntax(LR"(LOADBG [channel:int]{-[layer:int]} [clip:string] {[loop:LOOP]} {[transition:CUT,MIX,PUSH,WIPE,SLIDE] [duration:int] {[tween:string]|linear} {[direction:LEFT,RIGHT]|RIGHT}|CUT 0} {SEEK [frame:int]} {LENGTH [frames:int]} {FILTER [filter:string]} {[auto:AUTO]})"); + sink.para() + ->text(L"Loads a producer in the background and prepares it for playout. ") + ->text(L"If no layer is specified the default layer index will be used."); + sink.para() + ->code(L"clip")->text(L" will be parsed by available registered producer factories. ") + ->text(L"If a successfully match is found, the producer will be loaded into the background."); + sink.para() + ->text(L"If a file with the same name (extension excluded) but with the additional postfix ") + ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L"."); + sink.para() + ->code(L"loop")->text(L" will cause the clip to loop."); + sink.para() + ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L"."); + sink.para() + ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames."); + sink.para() + ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ") + ->text(LR"(The clip is considered "started" after the optional transition has ended.)"); + sink.para()->text(L"Examples:"); + sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip"); + sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE"); + sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT"); + sink.example(L">> LOADBG 1-0 MY_FILE"); + sink.example( + L">> PLAY 1-1 MY_FILE\n" + L">> LOADBG 1-1 EMPTY MIX 20 AUTO", + L"To automatically fade out a layer after a video file has been played to the end"); + sink.para() + ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L"."); + sink.para() + ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ") + ->code(L"filter")->text(L" command."); } -bool ChannelGridCommand::DoExecute() +std::wstring loadbg_command(command_context& ctx) { - int index = 1; - auto self = channels().back().channel; - - std::vector params; - params.push_back(L"SCREEN"); - params.push_back(L"0"); - params.push_back(L"NAME"); - params.push_back(L"Channel Grid Window"); - auto screen = create_consumer(params); + transition_info transitionInfo; - self->output().add(screen); + // TRANSITION - BOOST_FOREACH(auto channel, channels()) - { - if(channel.channel != self) - { - auto producer = reroute::create_producer(*channel.channel); - self->stage().load(index, producer, false); - self->stage().play(index); - index++; - } - } + std::wstring message; + for (size_t n = 0; n < ctx.parameters.size(); ++n) + message += boost::to_upper_copy(ctx.parameters[n]) + L" "; - int n = channels().size()-1; - double delta = 1.0/static_cast(n); - for(int x = 0; x < n; ++x) + static const boost::wregex expr(LR"(.*(?CUT|PUSH|SLIDE|WIPE|MIX)\s*(?\d+)\s*(?(LINEAR)|(EASE[^\s]*))?\s*(?FROMLEFT|FROMRIGHT|LEFT|RIGHT)?.*)"); + boost::wsmatch what; + if (boost::regex_match(message, what, expr)) { - for(int y = 0; y < n; ++y) - { - int index = x+y*n+1; - auto transform = [=](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; - }; - self->stage().apply_transform(index, transform); - } - } - - return true; -} + auto transition = what["TRANSITION"].str(); + transitionInfo.duration = boost::lexical_cast(what["DURATION"].str()); + auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L""; + auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L""; + transitionInfo.tweener = tween; -bool CallCommand::DoExecute() -{ - //Perform loading of the clip - try - { - auto result = channel()->stage().call(layer_index(), parameters()); - - if (result.wait_for(std::chrono::seconds(2)) != std::future_status::ready) - CASPAR_THROW_EXCEPTION(timed_out()); - - std::wstringstream replyString; - if(result.get().empty()) - replyString << TEXT("202 CALL OK\r\n"); - else - replyString << TEXT("201 CALL OK\r\n") << result.get() << L"\r\n"; - - SetReplyString(replyString.str()); + if (transition == L"CUT") + transitionInfo.type = transition_type::cut; + else if (transition == L"MIX") + transitionInfo.type = transition_type::mix; + else if (transition == L"PUSH") + transitionInfo.type = transition_type::push; + else if (transition == L"SLIDE") + transitionInfo.type = transition_type::slide; + else if (transition == L"WIPE") + transitionInfo.type = transition_type::wipe; - return true; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 CALL FAILED\r\n")); - return false; + if (direction == L"FROMLEFT") + transitionInfo.direction = transition_direction::from_left; + else if (direction == L"FROMRIGHT") + transitionInfo.direction = transition_direction::from_right; + else if (direction == L"LEFT") + transitionInfo.direction = transition_direction::from_right; + else if (direction == L"RIGHT") + transitionInfo.direction = transition_direction::from_left; } -} -tbb::concurrent_unordered_map> deferred_transforms; - -bool MixerCommand::DoExecute() -{ //Perform loading of the clip - try - { - bool defer = boost::iequals(parameters().back(), L"DEFER"); - if(defer) - parameters().pop_back(); - - std::vector transforms; - - if(boost::iequals(parameters()[0], L"KEYER") || boost::iequals(parameters()[0], L"IS_KEY")) - { - bool value = boost::lexical_cast(parameters().at(1)); - transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.is_key = value; - return transform; - }, 0, L"linear")); - } - else if(boost::iequals(parameters()[0], L"OPACITY")) - { - int duration = parameters().size() > 2 ? boost::lexical_cast(parameters()[2]) : 0; - std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear"; + 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(); - double value = boost::lexical_cast(parameters().at(1)); - - transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.opacity = value; - return transform; - }, duration, tween)); - } - else if(boost::iequals(parameters()[0], L"FILL") || boost::iequals(parameters()[0], L"FILL_RECT")) - { - int duration = parameters().size() > 5 ? boost::lexical_cast(parameters()[5]) : 0; - std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear"; - double x = boost::lexical_cast(parameters().at(1)); - double y = boost::lexical_cast(parameters().at(2)); - double x_s = boost::lexical_cast(parameters().at(3)); - double y_s = boost::lexical_cast(parameters().at(4)); - - transforms.push_back(stage::transform_tuple_t(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)); - } - else if(boost::iequals(parameters()[0], L"CLIP") || boost::iequals(parameters()[0], L"CLIP_RECT")) - { - int duration = parameters().size() > 5 ? boost::lexical_cast(parameters()[5]) : 0; - std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear"; - double x = boost::lexical_cast(parameters().at(1)); - double y = boost::lexical_cast(parameters().at(2)); - double x_s = boost::lexical_cast(parameters().at(3)); - double y_s = boost::lexical_cast(parameters().at(4)); - - transforms.push_back(stage::transform_tuple_t(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)); - } - else if(boost::iequals(parameters()[0], L"GRID")) - { - int duration = parameters().size() > 2 ? boost::lexical_cast(parameters()[2]) : 0; - std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear"; - int n = boost::lexical_cast(parameters().at(1)); - double delta = 1.0/static_cast(n); - for(int x = 0; x < n; ++x) - { - for(int y = 0; y < n; ++y) - { - int index = x+y*n+1; - transforms.push_back(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)); - } - } - } - else if(boost::iequals(parameters()[0], L"BLEND")) - { - auto blend_str = parameters().at(1); - int layer = layer_index(); - channel()->mixer().set_blend_mode(layer, get_blend_mode(blend_str)); - } - else if(boost::iequals(parameters()[0], L"MASTERVOLUME")) - { - float master_volume = boost::lexical_cast(parameters().at(1)); - channel()->mixer().set_master_volume(master_volume); - } - else if(boost::iequals(parameters()[0], L"BRIGHTNESS")) - { - auto value = boost::lexical_cast(parameters().at(1)); - int duration = parameters().size() > 2 ? boost::lexical_cast(parameters()[2]) : 0; - std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear"; - transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.brightness = value; - return transform; - }, duration, tween)); - } - else if(boost::iequals(parameters()[0], L"SATURATION")) - { - auto value = boost::lexical_cast(parameters().at(1)); - int duration = parameters().size() > 2 ? boost::lexical_cast(parameters()[2]) : 0; - std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear"; - transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.saturation = value; - return transform; - }, duration, tween)); - } - else if(parameters()[0] == L"CONTRAST") - { - auto value = boost::lexical_cast(parameters().at(1)); - int duration = parameters().size() > 2 ? boost::lexical_cast(parameters()[2]) : 0; - std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear"; - transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.contrast = value; - return transform; - }, duration, tween)); - } - else if(boost::iequals(parameters()[0], L"LEVELS")) - { - levels value; - value.min_input = boost::lexical_cast(parameters().at(1)); - value.max_input = boost::lexical_cast(parameters().at(2)); - value.gamma = boost::lexical_cast(parameters().at(3)); - value.min_output = boost::lexical_cast(parameters().at(4)); - value.max_output = boost::lexical_cast(parameters().at(5)); - int duration = parameters().size() > 6 ? boost::lexical_cast(parameters()[6]) : 0; - std::wstring tween = parameters().size() > 7 ? parameters()[7] : L"linear"; - - transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.levels = value; - return transform; - }, duration, tween)); - } - else if(boost::iequals(parameters()[0], L"VOLUME")) - { - int duration = parameters().size() > 2 ? boost::lexical_cast(parameters()[2]) : 0; - std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear"; - double value = boost::lexical_cast(parameters()[1]); + auto channel = ctx.channel.channel; + auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters); - transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform - { - transform.audio_transform.volume = value; - return transform; - }, duration, tween)); - } - else if(boost::iequals(parameters()[0], L"CLEAR")) - { - int layer = layer_index(std::numeric_limits::max()); + if (pFP == frame_producer::empty()) + CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L"")); - if (layer == std::numeric_limits::max()) - { - channel()->stage().clear_transforms(); - channel()->mixer().clear_blend_modes(); - } - else - { - channel()->stage().clear_transforms(layer); - channel()->mixer().clear_blend_mode(layer); - } - } - else if(boost::iequals(parameters()[0], L"COMMIT")) - { - transforms = std::move(deferred_transforms[channel_index()]); - } - else - { - SetReplyString(TEXT("404 MIXER ERROR\r\n")); - return false; - } + bool auto_play = contains_param(L"AUTO", ctx.parameters); - if(defer) - { - auto& defer_tranforms = deferred_transforms[channel_index()]; - defer_tranforms.insert(defer_tranforms.end(), transforms.begin(), transforms.end()); - } - else - channel()->stage().apply_transforms(transforms); - - SetReplyString(TEXT("202 MIXER OK\r\n")); + auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo); + if (auto_play) + channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP + else + channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("404 MIXER ERROR\r\n")); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 MIXER FAILED\r\n")); - return false; - } + return L"202 LOADBG OK\r\n"; } -bool SwapCommand::DoExecute() -{ - //Perform loading of the clip - try - { - if(layer_index(-1) != -1) - { - std::vector strs; - boost::split(strs, parameters()[0], boost::is_any_of("-")); - - auto ch1 = channel(); - auto ch2 = channels().at(boost::lexical_cast(strs.at(0))-1); +void load_describer(core::help_sink& sink, const core::help_repository& repo) +{ + sink.short_description(L"Load a media file or resource to the foreground."); + sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})"); + sink.para() + ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ") + ->text(L"If any clip is playing on the target foreground then this clip will be replaced."); + sink.para()->text(L"Examples:"); + sink.example(L">> LOAD 1 MY_FILE"); + sink.example(L">> LOAD 1-1 MY_FILE"); + sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details."); +} - int l1 = layer_index(); - int l2 = boost::lexical_cast(strs.at(1)); +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); + ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true); - ch1->stage().swap_layer(l1, l2, ch2.channel->stage()); - } - else - { - auto ch1 = channel(); - auto ch2 = channels().at(boost::lexical_cast(parameters()[0])-1); - ch1->stage().swap_layers(ch2.channel->stage()); - } - - SetReplyString(TEXT("202 SWAP OK\r\n")); + return L"202 LOAD OK\r\n"; +} - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("404 SWAP ERROR\r\n")); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 SWAP FAILED\r\n")); - return false; - } +void play_describer(core::help_sink& sink, const core::help_repository& repository) +{ + sink.short_description(L"Play a media file or resource."); + sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})"); + sink.para() + ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG") + ->text(L") is prepared, it will be executed."); + sink.para() + ->text(L"If additional parameters (see ")->see(L"LOADBG") + ->text(L") are provided then the provided clip will first be loaded to the background."); + sink.para()->text(L"Examples:"); + sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE"); + sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT"); + sink.example(L">> PLAY 1-0 MY_FILE"); + sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details."); } -bool AddCommand::DoExecute() -{ - //Perform loading of the clip - try - { - //create_consumer still expects all parameters to be uppercase - BOOST_FOREACH(std::wstring& str, parameters()) - { - boost::to_upper(str); - } +std::wstring play_command(command_context& ctx) +{ + if (!ctx.parameters.empty()) + loadbg_command(ctx); - auto consumer = create_consumer(parameters()); - channel()->output().add(layer_index(consumer->index()), consumer); - - SetReplyString(TEXT("202 ADD OK\r\n")); + ctx.channel.channel->stage().play(ctx.layer_index()); - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("404 ADD ERROR\r\n")); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 ADD FAILED\r\n")); - return false; - } + return L"202 PLAY OK\r\n"; } -bool RemoveCommand::DoExecute() -{ - //Perform loading of the clip - try - { - auto index = layer_index(std::numeric_limits::min()); - if(index == std::numeric_limits::min()) - { - //create_consumer still expects all parameters to be uppercase - BOOST_FOREACH(std::wstring& str, parameters()) - { - boost::to_upper(str); - } +void pause_describer(core::help_sink& sink, const core::help_repository& repo) +{ + sink.short_description(L"Pause playback of a layer."); + sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}"); + sink.para() + ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME") + ->text(L" command can be used to resume playback again."); + sink.para()->text(L"Examples:"); + sink.example(L">> PAUSE 1"); + sink.example(L">> PAUSE 1-1"); +} - index = create_consumer(parameters())->index(); - } +std::wstring pause_command(command_context& ctx) +{ + ctx.channel.channel->stage().pause(ctx.layer_index()); + return L"202 PAUSE OK\r\n"; +} - channel()->output().remove(index); +void resume_describer(core::help_sink& sink, const core::help_repository& repo) +{ + sink.short_description(L"Resume playback of a layer."); + sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}"); + sink.para() + ->text(L"Resumes playback of a foreground clip previously paused with the ") + ->see(L"PAUSE")->text(L" command."); + sink.para()->text(L"Examples:"); + sink.example(L">> RESUME 1"); + sink.example(L">> RESUME 1-1"); +} - SetReplyString(TEXT("202 REMOVE OK\r\n")); +std::wstring resume_command(command_context& ctx) +{ + ctx.channel.channel->stage().resume(ctx.layer_index()); + return L"202 RESUME OK\r\n"; +} - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("404 REMOVE ERROR\r\n")); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 REMOVE FAILED\r\n")); - return false; - } +void stop_describer(core::help_sink& sink, const core::help_repository& repo) +{ + sink.short_description(L"Remove the foreground clip of a layer."); + sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}"); + sink.para() + ->text(L"Removes the foreground clip of the specified layer."); + sink.para()->text(L"Examples:"); + sink.example(L">> STOP 1"); + sink.example(L">> STOP 1-1"); } -bool LoadCommand::DoExecute() -{ - //Perform loading of the clip - try - { - auto pFP = create_producer(channel()->frame_factory(), channel()->video_format_desc(), parameters()); - channel()->stage().load(layer_index(), pFP, true); - - SetReplyString(TEXT("202 LOAD OK\r\n")); +std::wstring stop_command(command_context& ctx) +{ + ctx.channel.channel->stage().stop(ctx.layer_index()); + return L"202 STOP OK\r\n"; +} - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("404 LOAD ERROR\r\n")); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 LOAD FAILED\r\n")); - return false; - } +void clear_describer(core::help_sink& sink, const core::help_repository& repo) +{ + sink.short_description(L"Remove all clips of a layer or an entire channel."); + sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}"); + sink.para() + ->text(L"Removes all clips (both foreground and background) of the specified layer. ") + ->text(L"If no layer is specified then all layers in the specified ") + ->code(L"video_channel")->text(L" are cleared."); + sink.para()->text(L"Examples:"); + sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1."); + sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1."); } +std::wstring clear_command(command_context& ctx) +{ + int index = ctx.layer_index(std::numeric_limits::min()); + if (index != std::numeric_limits::min()) + ctx.channel.channel->stage().clear(index); + else + ctx.channel.channel->stage().clear(); + return L"202 CLEAR OK\r\n"; +} -//std::function channel_cg_add_command::parse(const std::wstring& message, const std::vector& channels) -//{ -// static boost::wregex expr(L"^CG\\s(?\\d+)-?(?\\d+)?\\sADD\\s(?\\d+)\\s(?