X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=protocol%2Famcp%2FAMCPCommandsImpl.cpp;h=2788325c7dda4b7ed16e0d8f500bff366d8e5556;hb=9e4b08cde6c6de9e83a3fff42d90affc3cd8e5bc;hp=f1e8e876840ac1d34e27638f14c60153b7c4abf1;hpb=71489ae0dfe6ec4259e882f16e227f8b1ee2121d;p=casparcg diff --git a/protocol/amcp/AMCPCommandsImpl.cpp b/protocol/amcp/AMCPCommandsImpl.cpp index f1e8e8768..2788325c7 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 @@ -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); @@ -173,7 +173,7 @@ std::wstring read_latin1_file(const boost::filesystem::wpath& file) 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}; @@ -197,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()); @@ -267,1113 +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_)); -} - -bool DiagnosticsCommand::DoExecute() -{ - try - { - core::diagnostics::osd::show_graphs(true); - - SetReplyString(TEXT("202 DIAG OK\r\n")); - - return true; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 DIAG FAILED\r\n")); - return false; - } + return cpplinq::from(ctx.channels) + .select([](channel_context c) { return spl::make_shared_ptr(c.channel); }) + .to_vector(); } -bool ChannelGridCommand::DoExecute() +core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr& channel, const command_context& ctx) { - int index = 1; - auto self = channels().back().channel; - - core::diagnostics::scoped_call_context save; - core::diagnostics::call_context::for_thread().video_channel = channels().size(); - - 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); - - self->output().add(screen); - - for (auto& channel : channels()) - { - if(channel.channel != self) - { - core::diagnostics::call_context::for_thread().layer = index; - auto producer = reroute::create_producer(*channel.channel); - self->stage().load(index, producer, false); - self->stage().play(index); - index++; - } - } - - int n = channels().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 result = channel()->stage().call(layer_index(), parameters()); - - // TODO: because of std::async deferred timed waiting does not work - - /*auto wait_res = result.wait_for(std::chrono::seconds(2)); - if (wait_res == std::future_status::timeout) - 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()); - - return true; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(TEXT("502 CALL FAILED\r\n")); - return false; - } -} - -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"; - - 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]); - - 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 (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; - } - - 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")); - - 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; - } -} - -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); - - int l1 = layer_index(); - int l2 = boost::lexical_cast(strs.at(1)); - - 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 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; - } -} - -bool AddCommand::DoExecute() -{ - //Perform loading of the clip - try - { - //create_consumer still expects all parameters to be uppercase - for (auto& str : parameters()) - { - boost::to_upper(str); - } - - core::diagnostics::scoped_call_context save; - core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1; - - auto consumer = create_consumer(parameters()); - channel()->output().add(layer_index(consumer->index()), consumer); - - SetReplyString(TEXT("202 ADD OK\r\n")); - - 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; - } -} - -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 - for (auto& str : parameters()) - { - boost::to_upper(str); - } - - index = create_consumer(parameters())->index(); - } - - channel()->output().remove(index); - - SetReplyString(TEXT("202 REMOVE 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; - } + return core::frame_producer_dependencies( + channel->frame_factory(), + get_channels(ctx), + channel->video_format_desc(), + ctx.producer_registry); } -bool LoadCommand::DoExecute() -{ - //Perform loading of the clip - try - { - core::diagnostics::scoped_call_context save; - core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1; - core::diagnostics::call_context::for_thread().layer = layer_index(); - 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")); +// Basic Commands - 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 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."); } - - -//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(?