X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=protocol%2Famcp%2FAMCPCommandsImpl.cpp;h=fb4631f9d388836282ff93e5fcc9febeb966796f;hb=32cd8ffb795f8848a51365e4ab00a4a676b4123c;hp=8e4e759fcbcd1014efda870014761b3d83c08c0b;hpb=a9053d2524c1f406c0f477adf0f57881a0bdd28b;p=casparcg diff --git a/protocol/amcp/AMCPCommandsImpl.cpp b/protocol/amcp/AMCPCommandsImpl.cpp index 8e4e759fc..fb4631f9d 100644 --- a/protocol/amcp/AMCPCommandsImpl.cpp +++ b/protocol/amcp/AMCPCommandsImpl.cpp @@ -26,43 +26,47 @@ #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 #include @@ -74,36 +78,58 @@ #include #include #include +#include +#include +#include #include /* Return codes -100 [action] Information om att något har hänt -101 [action] Information om att något har hänt, en rad data skickas +102 [action] Information that [action] has happened +101 [action] Information that [action] has happened plus one row of data -202 [kommando] OK Kommandot har utförts -201 [kommando] OK Kommandot har utförts, och en rad data skickas tillbaka -200 [kommando] OK Kommandot har utförts, och flera rader data skickas tillbaka. Avslutas med tomrad +202 [command] OK [command] has been executed +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 Kommandot kunde inte förstås -401 [kommando] ERROR Ogiltig kanal -402 [kommando] ERROR Parameter saknas -403 [kommando] ERROR Ogiltig parameter -404 [kommando] ERROR Mediafilen hittades inte +400 ERROR the command could not be understood +401 [command] ERROR invalid/missing channel +402 [command] ERROR parameter missing +403 [command] ERROR invalid parameter +404 [command] ERROR file not found -500 FAILED Internt configurationfel -501 [kommando] FAILED Internt configurationfel -502 [kommando] FAILED Oläslig mediafil +500 FAILED internal error +501 [command] FAILED internal error +502 [command] FAILED could not read file +503 [command] FAILED access denied -600 [kommando] FAILED funktion ej implementerad +600 [command] FAILED [command] not implemented */ -namespace caspar { namespace protocol { +namespace caspar { namespace protocol { namespace amcp { using namespace core; -std::wstring read_utf8_file(const boost::filesystem::wpath& file) +std::wstring read_file_base64(const boost::filesystem::path& file) +{ + using namespace boost::archive::iterators; + + boost::filesystem::ifstream filestream(file, std::ios::binary); + + if (!filestream) + return L""; + + auto length = boost::filesystem::file_size(file); + std::vector bytes; + bytes.resize(length); + filestream.read(bytes.data(), length); + + std::string result(to_base64(bytes.data(), length)); + return std::wstring(result.begin(), result.end()); +} + +std::wstring read_utf8_file(const boost::filesystem::path& file) { std::wstringstream result; boost::filesystem::wifstream filestream(file); @@ -119,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); @@ -139,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}; @@ -173,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()); @@ -243,1411 +261,2672 @@ 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 dir = relativePath.parent_path(); + auto file = boost::to_upper_copy(relativePath.filename().wstring()); + relativePath = dir / file; - auto str = relativePath.replace_extension(TEXT("")).native(); + 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 { - -AMCPCommand::AMCPCommand() : channelIndex_(0), layerIndex_(-1) -{} - -void AMCPCommand::SendReply() +core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr& channel, const command_context& ctx) { - if(!pClientInfo_) - return; - - if(replyString_.empty()) - return; - pClientInfo_->Send(replyString_); + return core::frame_producer_dependencies( + channel->frame_factory(), + cpplinq::from(ctx.channels) + .select([](channel_context c) { return spl::make_shared_ptr(c.channel); }) + .to_vector(), + channel->video_format_desc(), + ctx.producer_registry); } -void AMCPCommand::Clear() +// Basic Commands + +void loadbg_describer(core::help_sink& sink, const core::help_repository& repository) { - pChannel_->stage().clear(); - pClientInfo_.reset(); - channelIndex_ = 0; - _parameters.clear(); + 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 DiagnosticsCommand::DoExecute() -{ - try - { - diagnostics::show_graphs(true); +std::wstring loadbg_command(command_context& ctx) +{ + transition_info transitionInfo; + + // TRANSITION - SetReplyString(TEXT("202 DIAG OK\r\n")); + std::wstring message; + for (size_t n = 0; n < ctx.parameters.size(); ++n) + message += boost::to_upper_copy(ctx.parameters[n]) + L" "; - return true; - } - catch(...) + 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)) { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 DIAG FAILED\r\n")); - return false; - } -} + 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 ChannelGridCommand::DoExecute() -{ - CASPAR_THROW_EXCEPTION(not_implemented()); - - //int index = 1; - //auto self = GetChannels().back(); - // - //std::vector params; - //params.push_back(L"SCREEN"); - //params.push_back(L"NAME"); - //params.push_back(L"Channel Grid Window"); - //auto screen = create_consumer(params); - - //self->output().add(screen); - - //BOOST_FOREACH(auto channel, GetChannels()) - //{ - // if(channel != self) - // { - // auto producer = reroute::create_producer(self->frame_factory(), *channel); - // self->stage().load(index, producer, false); - // self->stage().play(index); - // index++; - // } - //} - - //int n = GetChannels().size()-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; - // 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; -} - -bool CallCommand::DoExecute() -{ - //Perform loading of the clip - try - { - auto what = _parameters.at(0); - - std::wstring param; - for(auto it = std::begin(_parameters2); it != std::end(_parameters2); ++it, param += L" ") - param += *it; - - auto result = GetChannel()->stage().call(GetLayerIndex(), boost::trim_copy(param)); - - if(!result.timed_wait(boost::posix_time::seconds(2))) - 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 = _parameters.back() == L"DEFER"; - if(defer) - _parameters.pop_back(); - - std::vector transforms; + 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(); - if(_parameters[0] == L"KEYER" || _parameters[0] == L"IS_KEY") - { - bool value = boost::lexical_cast(_parameters.at(1)); - transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.is_key = value; - return transform; - }, 0, L"linear")); - } - else if(_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"; + auto channel = ctx.channel.channel; + auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters); - double value = boost::lexical_cast(_parameters.at(1)); - - transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.opacity = value; - return transform; - }, duration, tween)); - } - else if(_parameters[0] == L"FILL" || _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(GetLayerIndex(), [=](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; - 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(_parameters[0] == L"CLIP" || _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(GetLayerIndex(), [=](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(_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(_parameters[0] == L"BLEND") - { - auto blend_str = _parameters.at(1); - int layer = GetLayerIndex(); - GetChannel()->mixer().set_blend_mode(GetLayerIndex(), get_blend_mode(blend_str)); - } - else if(_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(GetLayerIndex(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.brightness = value; - return transform; - }, duration, tween)); - } - else if(_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(GetLayerIndex(), [=](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(GetLayerIndex(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.contrast = value; - return transform; - }, duration, tween)); - } - else if(_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(GetLayerIndex(), [=](frame_transform transform) -> frame_transform - { - transform.image_transform.levels = value; - return transform; - }, duration, tween)); - } - else if(_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]); + if (pFP == frame_producer::empty()) + CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L"")); - transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) -> frame_transform - { - transform.audio_transform.volume = value; - return transform; - }, duration, tween)); - } - else if(_parameters[0] == L"CLEAR") - { - int layer = GetLayerIndex(std::numeric_limits::max()); - if(layer == std::numeric_limits::max()) - GetChannel()->stage().clear_transforms(); - else - GetChannel()->stage().clear_transforms(layer); - } - else if(_parameters[0] == L"COMMIT") - { - transforms = std::move(deferred_transforms[GetChannelIndex()]); - } - 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[GetChannelIndex()]; - defer_tranforms.insert(defer_tranforms.end(), transforms.begin(), transforms.end()); - } - else - GetChannel()->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(GetLayerIndex(-1) != -1) - { - std::vector strs; - boost::split(strs, _parameters[0], boost::is_any_of("-")); - - auto ch1 = GetChannel(); - auto ch2 = GetChannels().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 = GetLayerIndex(); - 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->stage()); - } - else - { - auto ch1 = GetChannel(); - auto ch2 = GetChannels().at(boost::lexical_cast(_parameters[0])-1); - ch1->stage().swap_layers(ch2->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 - { - auto consumer = create_consumer(_parameters); - GetChannel()->output().add(GetLayerIndex(consumer->index()), consumer); - - SetReplyString(TEXT("202 ADD OK\r\n")); +std::wstring play_command(command_context& ctx) +{ + if (!ctx.parameters.empty()) + loadbg_command(ctx); - 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; - } + ctx.channel.channel->stage().play(ctx.layer_index()); + + return L"202 PLAY OK\r\n"; } -bool RemoveCommand::DoExecute() -{ - //Perform loading of the clip - try - { - auto index = GetLayerIndex(std::numeric_limits::min()); - if(index == std::numeric_limits::min()) - index = create_consumer(_parameters)->index(); +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"); +} + +std::wstring pause_command(command_context& ctx) +{ + ctx.channel.channel->stage().pause(ctx.layer_index()); + return L"202 PAUSE OK\r\n"; +} - GetChannel()->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 - { - _parameters[0] = _parameters[0]; - auto pFP = create_producer(GetChannel()->frame_factory(), GetChannel()->video_format_desc(), _parameters); - GetChannel()->stage().load(GetLayerIndex(), 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(?