X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=protocol%2Famcp%2FAMCPCommandsImpl.cpp;h=69826476d6e7c443c2ede347314a0bd5c3c0589f;hb=a9baf9ba1e7e4b94b5e08385328441de6607ad23;hp=48f323ba3fa93a69071e459241debcbcdcae9f14;hpb=15b3dad6661649f53562bcea673ba62e605d2b0a;p=casparcg diff --git a/protocol/amcp/AMCPCommandsImpl.cpp b/protocol/amcp/AMCPCommandsImpl.cpp index 48f323ba3..69826476d 100644 --- a/protocol/amcp/AMCPCommandsImpl.cpp +++ b/protocol/amcp/AMCPCommandsImpl.cpp @@ -26,19 +26,30 @@ #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 @@ -48,17 +59,7 @@ #include #include #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include #include @@ -86,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 @@ -106,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; @@ -128,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(); @@ -144,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); @@ -172,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}; @@ -208,8 +209,6 @@ std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_pt auto is_not_digit = [](char c){ return std::isdigit(c) == 0; }; - auto relativePath = boost::filesystem::wpath(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size())); - 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()); @@ -218,7 +217,9 @@ std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_pt 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(L"").generic_wstring(); + 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()); @@ -232,24 +233,37 @@ std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_pt + L"\r\n"; } -std::wstring ListMedia(const spl::shared_ptr& media_info_repo) -{ +std::wstring get_sub_directory(const std::wstring& base_folder, const std::wstring& sub_directory) +{ + if (sub_directory.empty()) + return base_folder; + + auto found = find_case_insensitive(base_folder + L"/" + sub_directory); + + if (!found) + CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Sub directory " + sub_directory + L" not found.")); + + return *found; +} + +std::wstring ListMedia(const spl::shared_ptr& media_info_repo, const std::wstring& sub_directory = L"") +{ std::wstringstream replyString; - for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr) + for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::media_folder(), sub_directory)), 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, const std::wstring& sub_directory = L"") { 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")) + for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::template_folder(), sub_directory)), end; itr != end; ++itr) + { + 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()); @@ -260,1113 +274,603 @@ std::wstring ListTemplates() auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end()); - std::wstring dir = relativePath.parent_path().generic_wstring(); - std::wstring file = boost::to_upper_copy(relativePath.filename().wstring()); - relativePath = boost::filesystem::wpath(dir + L"/" + file); - - auto str = relativePath.replace_extension(L"").generic_wstring(); + 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("\\/")); + 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(L"202 DIAG OK\r\n"); - - return true; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"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 << L"202 CALL OK\r\n"; - else - replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n"; - - SetReplyString(replyString.str()); - - return true; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"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(L"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(L"202 MIXER OK\r\n"); - - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"404 MIXER ERROR\r\n"); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"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(L"202 SWAP OK\r\n"); - - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"404 SWAP ERROR\r\n"); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"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(L"202 ADD OK\r\n"); - - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"404 ADD ERROR\r\n"); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"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(L"202 REMOVE OK\r\n"); - - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"404 REMOVE ERROR\r\n"); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"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, + ctx.cg_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(L"202 LOAD OK\r\n"); +// Basic Commands - return true; - } - catch(file_not_found&) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"404 LOAD ERROR\r\n"); - return false; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - SetReplyString(L"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(?