2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Nicklas P Andersson
22 #include "../StdAfx.h"
25 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
28 #include "AMCPCommandsImpl.h"
30 #include "amcp_command_repository.h"
31 #include "AMCPCommandQueue.h"
33 #include <common/env.h>
35 #include <common/log.h>
36 #include <common/param.h>
37 #include <common/os/system_info.h>
38 #include <common/os/filesystem.h>
39 #include <common/base64.h>
40 #include <common/thread_info.h>
42 #include <core/producer/cg_proxy.h>
43 #include <core/producer/frame_producer.h>
44 #include <core/help/help_repository.h>
45 #include <core/help/help_sink.h>
46 #include <core/help/util.h>
47 #include <core/video_format.h>
48 #include <core/producer/transition/transition_producer.h>
49 #include <core/frame/audio_channel_layout.h>
50 #include <core/frame/frame_transform.h>
51 #include <core/producer/text/text_producer.h>
52 #include <core/producer/stage.h>
53 #include <core/producer/layer.h>
54 #include <core/mixer/mixer.h>
55 #include <core/consumer/output.h>
56 #include <core/thumbnail_generator.h>
57 #include <core/producer/media_info/media_info.h>
58 #include <core/producer/media_info/media_info_repository.h>
59 #include <core/diagnostics/call_context.h>
60 #include <core/diagnostics/osd_graph.h>
61 #include <core/system_info_provider.h>
70 #include <boost/date_time/posix_time/posix_time.hpp>
71 #include <boost/lexical_cast.hpp>
72 #include <boost/algorithm/string.hpp>
73 #include <boost/filesystem.hpp>
74 #include <boost/filesystem/fstream.hpp>
75 #include <boost/regex.hpp>
76 #include <boost/property_tree/xml_parser.hpp>
77 #include <boost/locale.hpp>
78 #include <boost/range/adaptor/transformed.hpp>
79 #include <boost/range/algorithm/copy.hpp>
80 #include <boost/archive/iterators/base64_from_binary.hpp>
81 #include <boost/archive/iterators/insert_linebreaks.hpp>
82 #include <boost/archive/iterators/transform_width.hpp>
84 #include <tbb/concurrent_unordered_map.h>
88 102 [action] Information that [action] has happened
89 101 [action] Information that [action] has happened plus one row of data
91 202 [command] OK [command] has been executed
92 201 [command] OK [command] has been executed, plus one row of data
93 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
95 400 ERROR the command could not be understood
96 401 [command] ERROR invalid/missing channel
97 402 [command] ERROR parameter missing
98 403 [command] ERROR invalid parameter
99 404 [command] ERROR file not found
101 500 FAILED internal error
102 501 [command] FAILED internal error
103 502 [command] FAILED could not read file
104 503 [command] FAILED access denied
106 600 [command] FAILED [command] not implemented
109 namespace caspar { namespace protocol { namespace amcp {
111 using namespace core;
113 std::wstring read_file_base64(const boost::filesystem::path& file)
115 using namespace boost::archive::iterators;
117 boost::filesystem::ifstream filestream(file, std::ios::binary);
122 auto length = boost::filesystem::file_size(file);
123 std::vector<char> bytes;
124 bytes.resize(length);
125 filestream.read(bytes.data(), length);
127 std::string result(to_base64(bytes.data(), length));
128 return std::wstring(result.begin(), result.end());
131 std::wstring read_utf8_file(const boost::filesystem::path& file)
133 std::wstringstream result;
134 boost::filesystem::wifstream filestream(file);
141 result << filestream.rdbuf();
147 std::wstring read_latin1_file(const boost::filesystem::path& file)
149 boost::locale::generator gen;
150 gen.locale_cache_enabled(true);
151 gen.categories(boost::locale::codepage_facet);
153 std::stringstream result_stream;
154 boost::filesystem::ifstream filestream(file);
155 filestream.imbue(gen("en_US.ISO8859-1"));
160 result_stream << filestream.rdbuf();
163 std::string result = result_stream.str();
164 std::wstring widened_result;
166 // The first 255 codepoints in unicode is the same as in latin1
168 result | boost::adaptors::transformed(
169 [](char c) { return static_cast<unsigned char>(c); }),
170 std::back_inserter(widened_result));
172 return widened_result;
175 std::wstring read_file(const boost::filesystem::path& file)
177 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
179 if (!boost::filesystem::exists(file))
184 if (boost::filesystem::file_size(file) >= 3)
186 boost::filesystem::ifstream bom_stream(file);
189 bom_stream.read(header, 3);
192 if (std::memcmp(BOM, header, 3) == 0)
193 return read_utf8_file(file);
196 return read_latin1_file(file);
199 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
201 if (!boost::filesystem::is_regular_file(path))
204 auto media_info = media_info_repo->get(path.wstring());
209 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
211 auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
213 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
214 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
215 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
217 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
218 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
219 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
221 auto str = relativePath.replace_extension(L"").generic_wstring();
222 if (str[0] == '\\' || str[0] == '/')
223 str = std::wstring(str.begin() + 1, str.end());
225 return std::wstring()
227 + L"\" " + media_info->clip_type +
229 + L" " + writeTimeWStr +
230 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
231 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
235 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
237 std::wstringstream replyString;
238 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
239 replyString << MediaInfo(itr->path(), media_info_repo);
241 return boost::to_upper_copy(replyString.str());
244 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
246 std::wstringstream replyString;
248 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
250 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
252 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
254 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
255 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
256 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
258 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
259 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
261 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
263 auto dir = relativePath.parent_path();
264 auto file = boost::to_upper_copy(relativePath.filename().wstring());
265 relativePath = dir / file;
267 auto str = relativePath.replace_extension(L"").generic_wstring();
268 boost::trim_if(str, boost::is_any_of("\\/"));
270 auto template_type = cg_registry->get_cg_producer_name(str);
272 replyString << L"\"" << str
273 << L"\" " << sizeWStr
274 << L" " << writeTimeWStr
275 << L" " << template_type
279 return replyString.str();
282 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
284 return core::frame_producer_dependencies(
285 channel->frame_factory(),
286 cpplinq::from(ctx.channels)
287 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
289 channel->video_format_desc(),
290 ctx.producer_registry);
295 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
297 sink.short_description(L"Load a media file or resource in the background.");
298 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]})");
300 ->text(L"Loads a producer in the background and prepares it for playout. ")
301 ->text(L"If no layer is specified the default layer index will be used.");
303 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
304 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
306 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
307 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
309 ->code(L"loop")->text(L" will cause the clip to loop.");
311 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
313 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
315 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
316 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
317 sink.para()->text(L"Examples:");
318 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
319 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
320 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
321 sink.example(L">> LOADBG 1-0 MY_FILE");
323 L">> PLAY 1-1 MY_FILE\n"
324 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
325 L"To automatically fade out a layer after a video file has been played to the end");
327 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
329 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
330 ->code(L"filter")->text(L" command.");
333 std::wstring loadbg_command(command_context& ctx)
335 transition_info transitionInfo;
339 std::wstring message;
340 for (size_t n = 0; n < ctx.parameters.size(); ++n)
341 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
343 static const boost::wregex expr(LR"(.*(?<TRANSITION>CUT|PUSH|SLIDE|WIPE|MIX)\s*(?<DURATION>\d+)\s*(?<TWEEN>(LINEAR)|(EASE[^\s]*))?\s*(?<DIRECTION>FROMLEFT|FROMRIGHT|LEFT|RIGHT)?.*)");
345 if (boost::regex_match(message, what, expr))
347 auto transition = what["TRANSITION"].str();
348 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
349 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
350 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
351 transitionInfo.tweener = tween;
353 if (transition == L"CUT")
354 transitionInfo.type = transition_type::cut;
355 else if (transition == L"MIX")
356 transitionInfo.type = transition_type::mix;
357 else if (transition == L"PUSH")
358 transitionInfo.type = transition_type::push;
359 else if (transition == L"SLIDE")
360 transitionInfo.type = transition_type::slide;
361 else if (transition == L"WIPE")
362 transitionInfo.type = transition_type::wipe;
364 if (direction == L"FROMLEFT")
365 transitionInfo.direction = transition_direction::from_left;
366 else if (direction == L"FROMRIGHT")
367 transitionInfo.direction = transition_direction::from_right;
368 else if (direction == L"LEFT")
369 transitionInfo.direction = transition_direction::from_right;
370 else if (direction == L"RIGHT")
371 transitionInfo.direction = transition_direction::from_left;
374 //Perform loading of the clip
375 core::diagnostics::scoped_call_context save;
376 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
377 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
379 auto channel = ctx.channel.channel;
380 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
382 if (pFP == frame_producer::empty())
383 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
385 bool auto_play = contains_param(L"AUTO", ctx.parameters);
387 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
389 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
391 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
393 return L"202 LOADBG OK\r\n";
396 void load_describer(core::help_sink& sink, const core::help_repository& repo)
398 sink.short_description(L"Load a media file or resource to the foreground.");
399 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
401 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
402 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
403 sink.para()->text(L"Examples:");
404 sink.example(L">> LOAD 1 MY_FILE");
405 sink.example(L">> LOAD 1-1 MY_FILE");
406 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
409 std::wstring load_command(command_context& ctx)
411 core::diagnostics::scoped_call_context save;
412 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
413 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
414 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
415 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
417 return L"202 LOAD OK\r\n";
420 void play_describer(core::help_sink& sink, const core::help_repository& repository)
422 sink.short_description(L"Play a media file or resource.");
423 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
425 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
426 ->text(L") is prepared, it will be executed.");
428 ->text(L"If additional parameters (see ")->see(L"LOADBG")
429 ->text(L") are provided then the provided clip will first be loaded to the background.");
430 sink.para()->text(L"Examples:");
431 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
432 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
433 sink.example(L">> PLAY 1-0 MY_FILE");
434 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
437 std::wstring play_command(command_context& ctx)
439 if (!ctx.parameters.empty())
442 ctx.channel.channel->stage().play(ctx.layer_index());
444 return L"202 PLAY OK\r\n";
447 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
449 sink.short_description(L"Pause playback of a layer.");
450 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
452 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
453 ->text(L" command can be used to resume playback again.");
454 sink.para()->text(L"Examples:");
455 sink.example(L">> PAUSE 1");
456 sink.example(L">> PAUSE 1-1");
459 std::wstring pause_command(command_context& ctx)
461 ctx.channel.channel->stage().pause(ctx.layer_index());
462 return L"202 PAUSE OK\r\n";
465 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
467 sink.short_description(L"Resume playback of a layer.");
468 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
470 ->text(L"Resumes playback of a foreground clip previously paused with the ")
471 ->see(L"PAUSE")->text(L" command.");
472 sink.para()->text(L"Examples:");
473 sink.example(L">> RESUME 1");
474 sink.example(L">> RESUME 1-1");
477 std::wstring resume_command(command_context& ctx)
479 ctx.channel.channel->stage().resume(ctx.layer_index());
480 return L"202 RESUME OK\r\n";
483 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
485 sink.short_description(L"Remove the foreground clip of a layer.");
486 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
488 ->text(L"Removes the foreground clip of the specified layer.");
489 sink.para()->text(L"Examples:");
490 sink.example(L">> STOP 1");
491 sink.example(L">> STOP 1-1");
494 std::wstring stop_command(command_context& ctx)
496 ctx.channel.channel->stage().stop(ctx.layer_index());
497 return L"202 STOP OK\r\n";
500 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
502 sink.short_description(L"Remove all clips of a layer or an entire channel.");
503 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
505 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
506 ->text(L"If no layer is specified then all layers in the specified ")
507 ->code(L"video_channel")->text(L" are cleared.");
508 sink.para()->text(L"Examples:");
509 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
510 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
513 std::wstring clear_command(command_context& ctx)
515 int index = ctx.layer_index(std::numeric_limits<int>::min());
516 if (index != std::numeric_limits<int>::min())
517 ctx.channel.channel->stage().clear(index);
519 ctx.channel.channel->stage().clear();
521 return L"202 CLEAR OK\r\n";
524 void call_describer(core::help_sink& sink, const core::help_repository& repo)
526 sink.short_description(L"Call a method on a producer.");
527 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
529 ->text(L"Calls method on the specified producer with the provided ")
530 ->code(L"param")->text(L" string.");
531 sink.para()->text(L"Examples:");
532 sink.example(L">> CALL 1 LOOP");
533 sink.example(L">> CALL 1-2 SEEK 25");
536 std::wstring call_command(command_context& ctx)
538 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
540 // TODO: because of std::async deferred timed waiting does not work
542 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
543 if (wait_res == std::future_status::timeout)
544 CASPAR_THROW_EXCEPTION(timed_out());*/
546 std::wstringstream replyString;
547 if (result.get().empty())
548 replyString << L"202 CALL OK\r\n";
550 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
552 return replyString.str();
555 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
557 sink.short_description(L"Swap layers between channels.");
558 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
560 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
561 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
562 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
563 sink.para()->text(L"Examples:");
564 sink.example(L">> SWAP 1 2");
565 sink.example(L">> SWAP 1-1 2-3");
566 sink.example(L">> SWAP 1-1 1-2");
567 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
570 std::wstring swap_command(command_context& ctx)
572 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
574 if (ctx.layer_index(-1) != -1)
576 std::vector<std::string> strs;
577 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
579 auto ch1 = ctx.channel.channel;
580 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
582 int l1 = ctx.layer_index();
583 int l2 = boost::lexical_cast<int>(strs.at(1));
585 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
589 auto ch1 = ctx.channel.channel;
590 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
591 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
594 return L"202 SWAP OK\r\n";
597 void add_describer(core::help_sink& sink, const core::help_repository& repo)
599 sink.short_description(L"Add a consumer to a video channel.");
600 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
602 ->text(L"Adds a consumer to the specified video channel. The string ")
603 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
604 ->text(L"If a successful match is found a consumer will be created and added to the ")
605 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
606 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
607 ->see(L"the CasparCG config file")->text(L".");
608 sink.para()->text(L"Examples:");
609 sink.example(L">> ADD 1 DECKLINK 1");
610 sink.example(L">> ADD 1 BLUEFISH 2");
611 sink.example(L">> ADD 1 SCREEN");
612 sink.example(L">> ADD 1 AUDIO");
613 sink.example(L">> ADD 1 IMAGE filename");
614 sink.example(L">> ADD 1 FILE filename.mov");
615 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
616 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
617 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
620 std::wstring add_command(command_context& ctx)
622 replace_placeholders(
623 L"<CLIENT_IP_ADDRESS>",
624 ctx.client->address(),
627 core::diagnostics::scoped_call_context save;
628 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
630 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
631 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
633 return L"202 ADD OK\r\n";
636 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
638 sink.short_description(L"Remove a consumer from a video channel.");
639 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
641 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
642 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
643 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
644 sink.para()->text(L"Examples:");
645 sink.example(L">> REMOVE 1 DECKLINK 1");
646 sink.example(L">> REMOVE 1 BLUEFISH 2");
647 sink.example(L">> REMOVE 1 SCREEN");
648 sink.example(L">> REMOVE 1 AUDIO");
649 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
652 std::wstring remove_command(command_context& ctx)
654 auto index = ctx.layer_index(std::numeric_limits<int>::min());
656 if (index == std::numeric_limits<int>::min())
658 replace_placeholders(
659 L"<CLIENT_IP_ADDRESS>",
660 ctx.client->address(),
663 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
666 ctx.channel.channel->output().remove(index);
668 return L"202 REMOVE OK\r\n";
671 void print_describer(core::help_sink& sink, const core::help_repository& repo)
673 sink.short_description(L"Take a snapshot of a channel.");
674 sink.syntax(L"PRINT [video_channel:int]");
676 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
677 ->code(L"media")->text(L" folder.");
678 sink.para()->text(L"Examples:");
679 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
682 std::wstring print_command(command_context& ctx)
684 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
686 return L"202 PRINT OK\r\n";
689 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
691 sink.short_description(L"Change the log level of the server.");
692 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
693 sink.para()->text(L"Changes the log level of the server.");
694 sink.para()->text(L"Examples:");
695 sink.example(L">> LOG LEVEL trace");
696 sink.example(L">> LOG LEVEL info");
699 std::wstring log_level_command(command_context& ctx)
701 log::set_log_level(ctx.parameters.at(0));
703 return L"202 LOG OK\r\n";
706 void set_describer(core::help_sink& sink, const core::help_repository& repo)
708 sink.short_description(L"Change the value of a channel variable.");
709 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
710 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
712 ->item(L"MODE", L"Changes the video format of the channel.")
713 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
714 sink.para()->text(L"Examples:");
715 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
716 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
719 std::wstring set_command(command_context& ctx)
721 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
722 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
726 auto format_desc = core::video_format_desc(value);
727 if (format_desc.format != core::video_format::invalid)
729 ctx.channel.channel->video_format_desc(format_desc);
730 return L"202 SET MODE OK\r\n";
733 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
735 else if (name == L"CHANNEL_LAYOUT")
737 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
741 ctx.channel.channel->audio_channel_layout(*channel_layout);
742 return L"202 SET CHANNEL_LAYOUT OK\r\n";
745 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid audio channel layout"));
748 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
751 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
753 sink.short_description(L"Store a dataset.");
754 sink.syntax(L"DATA STORE [name:string] [data:string]");
755 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
756 sink.para()->text(L"Directories will be created if they do not exist.");
757 sink.para()->text(L"Examples:");
758 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
759 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
762 std::wstring data_store_command(command_context& ctx)
764 std::wstring filename = env::data_folder();
765 filename.append(ctx.parameters[0]);
766 filename.append(L".ftd");
768 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
769 auto found_data_path = find_case_insensitive(data_path);
772 data_path = *found_data_path;
774 if (!boost::filesystem::exists(data_path))
775 boost::filesystem::create_directories(data_path);
777 auto found_filename = find_case_insensitive(filename);
780 filename = *found_filename; // Overwrite case insensitive.
782 boost::filesystem::wofstream datafile(filename);
784 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
786 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
787 datafile << ctx.parameters[1] << std::flush;
790 return L"202 DATA STORE OK\r\n";
793 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
795 sink.short_description(L"Retrieve a dataset.");
796 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
797 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
798 sink.para()->text(L"Examples:");
799 sink.example(L">> DATA RETRIEVE my_data");
800 sink.example(L">> DATA RETRIEVE Folder1/my_data");
803 std::wstring data_retrieve_command(command_context& ctx)
805 std::wstring filename = env::data_folder();
806 filename.append(ctx.parameters[0]);
807 filename.append(L".ftd");
809 std::wstring file_contents;
811 auto found_file = find_case_insensitive(filename);
814 file_contents = read_file(boost::filesystem::path(*found_file));
816 if (file_contents.empty())
817 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
819 std::wstringstream reply;
820 reply << L"201 DATA RETRIEVE OK\r\n";
822 std::wstringstream file_contents_stream(file_contents);
825 bool firstLine = true;
826 while (std::getline(file_contents_stream, line))
840 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
842 sink.short_description(L"List stored datasets.");
843 sink.syntax(L"DATA LIST");
844 sink.para()->text(L"Returns a list of all stored datasets.");
847 std::wstring data_list_command(command_context& ctx)
849 std::wstringstream replyString;
850 replyString << L"200 DATA LIST OK\r\n";
852 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
854 if (boost::filesystem::is_regular_file(itr->path()))
856 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
859 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
861 auto str = relativePath.replace_extension(L"").generic_wstring();
862 if (str[0] == L'\\' || str[0] == L'/')
863 str = std::wstring(str.begin() + 1, str.end());
865 replyString << str << L"\r\n";
869 replyString << L"\r\n";
871 return boost::to_upper_copy(replyString.str());
874 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
876 sink.short_description(L"Remove a stored dataset.");
877 sink.syntax(L"DATA REMOVE [name:string]");
878 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
879 sink.para()->text(L"Examples:");
880 sink.example(L">> DATA REMOVE my_data");
881 sink.example(L">> DATA REMOVE Folder1/my_data");
884 std::wstring data_remove_command(command_context& ctx)
886 std::wstring filename = env::data_folder();
887 filename.append(ctx.parameters[0]);
888 filename.append(L".ftd");
890 if (!boost::filesystem::exists(filename))
891 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
893 if (!boost::filesystem::remove(filename))
894 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
896 return L"201 DATA REMOVE OK\r\n";
899 // Template Graphics Commands
901 int get_and_validate_layer(const std::wstring& layerstring) {
902 int length = layerstring.length();
903 for (int i = 0; i < length; ++i) {
904 if (!std::isdigit(layerstring[i])) {
905 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
909 return boost::lexical_cast<int>(layerstring);
912 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
914 sink.short_description(L"Prepare a template for displaying.");
915 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
917 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
918 ->text(L" (unless you supply the play-on-load flag, 1 for true). Data is either inline XML or a reference to a saved dataset.");
919 sink.para()->text(L"Examples:");
920 sink.example(L"CG 1 ADD 10 svtnews/info 1");
923 std::wstring cg_add_command(command_context& ctx)
925 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
927 int layer = get_and_validate_layer(ctx.parameters.at(0));
928 std::wstring label; //_parameters[2]
929 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
930 unsigned int dataIndex = 3;
932 if (ctx.parameters.at(2).length() > 1)
934 label = ctx.parameters.at(2);
937 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
938 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
941 { //read play-on-load-flag
942 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
945 const wchar_t* pDataString = 0;
946 std::wstring dataFromFile;
947 if (ctx.parameters.size() > dataIndex)
949 const std::wstring& dataString = ctx.parameters.at(dataIndex);
951 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
952 pDataString = dataString.c_str();
955 //The data is not an XML-string, it must be a filename
956 std::wstring filename = env::data_folder();
957 filename.append(dataString);
958 filename.append(L".ftd");
960 auto found_file = find_case_insensitive(filename);
964 dataFromFile = read_file(boost::filesystem::path(*found_file));
965 pDataString = dataFromFile.c_str();
970 auto filename = ctx.parameters.at(1);
971 auto proxy = ctx.cg_registry->get_or_create_proxy(
972 spl::make_shared_ptr(ctx.channel.channel),
973 get_producer_dependencies(ctx.channel.channel, ctx),
974 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
977 if (proxy == core::cg_proxy::empty())
978 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
980 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
982 return L"202 CG OK\r\n";
985 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
987 sink.short_description(L"Play and display a template.");
988 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
989 sink.para()->text(L"Plays and displays the template in the specified layer.");
990 sink.para()->text(L"Examples:");
991 sink.example(L"CG 1 PLAY 0");
994 std::wstring cg_play_command(command_context& ctx)
996 int layer = get_and_validate_layer(ctx.parameters.at(0));
997 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
999 return L"202 CG OK\r\n";
1002 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1004 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1006 if (proxy == cg_proxy::empty())
1007 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No CG proxy running on layer"));
1012 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1014 sink.short_description(L"Stop and remove a template.");
1015 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1017 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1018 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1019 sink.para()->text(L"Examples:");
1020 sink.example(L"CG 1 STOP 0");
1023 std::wstring cg_stop_command(command_context& ctx)
1025 int layer = get_and_validate_layer(ctx.parameters.at(0));
1026 get_expected_cg_proxy(ctx)->stop(layer, 0);
1028 return L"202 CG OK\r\n";
1031 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1033 sink.short_description(LR"(Trigger a "continue" in a template.)");
1034 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1036 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1037 ->text(L"This is used to control animations that has multiple discreet steps.");
1038 sink.para()->text(L"Examples:");
1039 sink.example(L"CG 1 NEXT 0");
1042 std::wstring cg_next_command(command_context& ctx)
1044 int layer = get_and_validate_layer(ctx.parameters.at(0));
1045 get_expected_cg_proxy(ctx)->next(layer);
1047 return L"202 CG OK\r\n";
1050 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1052 sink.short_description(L"Remove a template.");
1053 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1054 sink.para()->text(L"Removes the template from the specified layer.");
1055 sink.para()->text(L"Examples:");
1056 sink.example(L"CG 1 REMOVE 0");
1059 std::wstring cg_remove_command(command_context& ctx)
1061 int layer = get_and_validate_layer(ctx.parameters.at(0));
1062 get_expected_cg_proxy(ctx)->remove(layer);
1064 return L"202 CG OK\r\n";
1067 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1069 sink.short_description(L"Remove all templates on a video layer.");
1070 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1071 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1072 sink.para()->text(L"Examples:");
1073 sink.example(L"CG 1 CLEAR");
1076 std::wstring cg_clear_command(command_context& ctx)
1078 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1080 return L"202 CG OK\r\n";
1083 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1085 sink.short_description(L"Update a template with new data.");
1086 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1087 sink.para()->text(L"Sends new data to the template on specified layer. Data is either inline XML or a reference to a saved dataset.");
1090 std::wstring cg_update_command(command_context& ctx)
1092 int layer = get_and_validate_layer(ctx.parameters.at(0));
1094 std::wstring dataString = ctx.parameters.at(1);
1095 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1097 //The data is not XML or Json, it must be a filename
1098 std::wstring filename = env::data_folder();
1099 filename.append(dataString);
1100 filename.append(L".ftd");
1102 dataString = read_file(boost::filesystem::path(filename));
1105 get_expected_cg_proxy(ctx)->update(layer, dataString);
1107 return L"202 CG OK\r\n";
1110 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1112 sink.short_description(L"Invoke a method/label on a template.");
1113 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1114 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1115 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1118 std::wstring cg_invoke_command(command_context& ctx)
1120 std::wstringstream replyString;
1121 replyString << L"201 CG OK\r\n";
1122 int layer = get_and_validate_layer(ctx.parameters.at(0));
1123 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1124 replyString << result << L"\r\n";
1126 return replyString.str();
1129 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1131 sink.short_description(L"Get information about a running template or the template host.");
1132 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1133 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1134 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1137 std::wstring cg_info_command(command_context& ctx)
1139 std::wstringstream replyString;
1140 replyString << L"201 CG OK\r\n";
1142 if (ctx.parameters.empty())
1144 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1145 replyString << info << L"\r\n";
1149 int layer = get_and_validate_layer(ctx.parameters.at(0));
1150 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1152 replyString << desc << L"\r\n";
1155 return replyString.str();
1160 core::frame_transform get_current_transform(command_context& ctx)
1162 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1165 template<typename Func>
1166 std::wstring reply_value(command_context& ctx, const Func& extractor)
1168 auto value = extractor(get_current_transform(ctx));
1170 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1173 class transforms_applier
1175 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1177 std::vector<stage::transform_tuple_t> transforms_;
1178 command_context& ctx_;
1181 transforms_applier(command_context& ctx)
1184 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1187 ctx.parameters.pop_back();
1190 void add(stage::transform_tuple_t&& transform)
1192 transforms_.push_back(std::move(transform));
1195 void commit_deferred()
1197 auto& transforms = deferred_transforms_[ctx_.channel_index];
1198 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1206 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1207 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1210 ctx_.channel.channel->stage().apply_transforms(transforms_);
1213 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1215 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1217 sink.short_description(L"Let a layer act as alpha for the one obove.");
1218 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1220 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1221 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1222 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1223 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1224 ->text(L"instead it will be used as the key for the layer above.");
1225 sink.para()->text(L"Examples:");
1226 sink.example(L">> MIXER 1-0 KEYER 1");
1228 L">> MIXER 1-0 KEYER\n"
1229 L"<< 201 MIXER OK\n"
1230 L"<< 1", L"to retrieve the current state");
1233 std::wstring mixer_keyer_command(command_context& ctx)
1235 if (ctx.parameters.empty())
1236 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1238 transforms_applier transforms(ctx);
1239 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1240 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1242 transform.image_transform.is_key = value;
1247 return L"202 MIXER OK\r\n";
1250 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1252 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1254 sink.short_description(L"Enable chroma keying on a layer.");
1255 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1257 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1258 sink.para()->text(L"Examples:");
1259 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1260 sink.example(L">> MIXER 1-1 CHROMA none");
1262 L">> MIXER 1-1 BLEND\n"
1263 L"<< 201 MIXER OK\n"
1264 L"<< SCREEN", L"for getting the current blend mode");
1267 std::wstring mixer_chroma_command(command_context& ctx)
1269 if (ctx.parameters.empty())
1271 auto chroma = get_current_transform(ctx).image_transform.chroma;
1272 return L"201 MIXER OK\r\n"
1273 + core::get_chroma_mode(chroma.key) + L" "
1274 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1275 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1276 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1279 transforms_applier transforms(ctx);
1280 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1281 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1283 core::chroma chroma;
1284 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1286 if (chroma.key != core::chroma::type::none)
1288 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1289 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1290 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1293 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1295 transform.image_transform.chroma = chroma;
1297 }, duration, tween));
1300 return L"202 MIXER OK\r\n";
1303 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1305 sink.short_description(L"Set the blend mode for a layer.");
1306 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1308 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1309 ->text(L"If no argument is given the current blend mode is returned.");
1311 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1312 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1313 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1314 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1315 sink.para()->text(L"Examples:");
1316 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1318 L">> MIXER 1-1 BLEND\n"
1319 L"<< 201 MIXER OK\n"
1320 L"<< SCREEN", L"for getting the current blend mode");
1321 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1324 std::wstring mixer_blend_command(command_context& ctx)
1326 if (ctx.parameters.empty())
1327 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1329 transforms_applier transforms(ctx);
1330 auto value = get_blend_mode(ctx.parameters.at(0));
1331 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1333 transform.image_transform.blend_mode = value;
1338 return L"202 MIXER OK\r\n";
1341 template<typename Getter, typename Setter>
1342 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1344 if (ctx.parameters.empty())
1345 return reply_value(ctx, getter);
1347 transforms_applier transforms(ctx);
1348 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1349 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1350 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1352 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1354 setter(transform, value);
1356 }, duration, tween));
1359 return L"202 MIXER OK\r\n";
1362 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1364 sink.short_description(L"Change the opacity of a layer.");
1365 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1366 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1367 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1368 sink.para()->text(L"Examples:");
1369 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1371 L">> MIXER 1-0 OPACITY\n"
1372 L"<< 201 MIXER OK\n"
1373 L"<< 0.5", L"to retrieve the current opacity");
1376 std::wstring mixer_opacity_command(command_context& ctx)
1378 return single_double_animatable_mixer_command(
1380 [](const frame_transform& t) { return t.image_transform.opacity; },
1381 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1384 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1386 sink.short_description(L"Change the brightness of a layer.");
1387 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1388 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1389 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1390 sink.para()->text(L"Examples:");
1391 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1393 L">> MIXER 1-0 BRIGHTNESS\n"
1394 L"<< 201 MIXER OK\n"
1395 L"0.5", L"to retrieve the current brightness");
1398 std::wstring mixer_brightness_command(command_context& ctx)
1400 return single_double_animatable_mixer_command(
1402 [](const frame_transform& t) { return t.image_transform.brightness; },
1403 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1406 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1408 sink.short_description(L"Change the saturation of a layer.");
1409 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1410 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1411 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1412 sink.para()->text(L"Examples:");
1413 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1415 L">> MIXER 1-0 SATURATION\n"
1416 L"<< 201 MIXER OK\n"
1417 L"<< 0.5", L"to retrieve the current saturation");
1420 std::wstring mixer_saturation_command(command_context& ctx)
1422 return single_double_animatable_mixer_command(
1424 [](const frame_transform& t) { return t.image_transform.saturation; },
1425 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1428 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1430 sink.short_description(L"Change the contrast of a layer.");
1431 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1432 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1433 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1434 sink.para()->text(L"Examples:");
1435 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1437 L">> MIXER 1-0 CONTRAST\n"
1438 L"<< 201 MIXER OK\n"
1439 L"<< 0.5", L"to retrieve the current contrast");
1442 std::wstring mixer_contrast_command(command_context& ctx)
1444 return single_double_animatable_mixer_command(
1446 [](const frame_transform& t) { return t.image_transform.contrast; },
1447 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1450 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1452 sink.short_description(L"Adjust the video levels of a layer.");
1453 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} LEVELS {[min-input:float] [max-input:float] [gamma:float] [min-output:float] [max-output:float]" + ANIMATION_SYNTAX);
1455 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1457 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1458 ->item(L"gamma", L"Adjusts the gamma of the image.")
1459 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1460 sink.para()->text(L"Examples:");
1461 sink.example(L">> MIXER 1-10 LEVELS 0.0627 0.922 1 0 1 25 easeinsine", L"for stretching 16-235 video to 0-255 video");
1462 sink.example(L">> MIXER 1-10 LEVELS 0 1 1 0.0627 0.922 25 easeinsine", L"for compressing 0-255 video to 16-235 video");
1463 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1465 L">> MIXER 1-10 LEVELS\n"
1466 L"<< 201 MIXER OK\n"
1467 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1470 std::wstring mixer_levels_command(command_context& ctx)
1472 if (ctx.parameters.empty())
1474 auto levels = get_current_transform(ctx).image_transform.levels;
1475 return L"201 MIXER OK\r\n"
1476 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1477 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1478 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1479 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1480 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1483 transforms_applier transforms(ctx);
1485 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1486 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1487 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1488 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1489 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1490 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1491 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1493 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1495 transform.image_transform.levels = value;
1497 }, duration, tween));
1500 return L"202 MIXER OK\r\n";
1503 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1505 sink.short_description(L"Change the fill position and scale of a layer.");
1506 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1508 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1509 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1510 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1511 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1513 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1514 ->text(L"You set the left edge to full right => 1 and the width to 0. So this give you the start-coordinates of 1 0 0 1.");
1515 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1517 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1518 ->text(L"if you want to do a smaller window. If, for instance you want to have a window of half the size of your screen, ")
1519 ->text(L"you set with and height to 0.5. If you want to center it you set left and top edge to 0.25 so you will get the arguments 0.25 0.25 0.5 0.5");
1521 ->item(L"x", L"The new x position, 0 = left edge of monitor, 0.5 = middle of monitor, 1.0 = right edge of monitor. Higher and lower values allowed.")
1522 ->item(L"y", L"The new y position, 0 = top edge of monitor, 0.5 = middle of monitor, 1.0 = bottom edge of monitor. Higher and lower values allowed.")
1523 ->item(L"x-scale", L"The new x scale, 1 = 1x the screen width, 0.5 = half the screen width. Higher and lower values allowed. Negative values flips the layer.")
1524 ->item(L"y-scale", L"The new y scale, 1 = 1x the screen height, 0.5 = half the screen height. Higher and lower values allowed. Negative values flips the layer.");
1525 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1526 sink.para()->text(L"Examples:");
1527 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1529 L">> MIXER 1-0 FILL\n"
1530 L"<< 201 MIXER OK\n"
1531 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1534 std::wstring mixer_fill_command(command_context& ctx)
1536 if (ctx.parameters.empty())
1538 auto transform = get_current_transform(ctx).image_transform;
1539 auto translation = transform.fill_translation;
1540 auto scale = transform.fill_scale;
1541 return L"201 MIXER OK\r\n"
1542 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1543 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1544 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1545 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1548 transforms_applier transforms(ctx);
1549 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1550 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1551 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1552 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1553 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1554 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1556 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1558 transform.image_transform.fill_translation[0] = x;
1559 transform.image_transform.fill_translation[1] = y;
1560 transform.image_transform.fill_scale[0] = x_s;
1561 transform.image_transform.fill_scale[1] = y_s;
1563 }, duration, tween));
1566 return L"202 MIXER OK\r\n";
1569 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1571 sink.short_description(L"Change the clipping viewport of a layer.");
1572 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1574 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1575 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1576 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1578 ->item(L"x", L"The new x position, 0 = left edge of monitor, 0.5 = middle of monitor, 1.0 = right edge of monitor. Higher and lower values allowed.")
1579 ->item(L"y", L"The new y position, 0 = top edge of monitor, 0.5 = middle of monitor, 1.0 = bottom edge of monitor. Higher and lower values allowed.")
1580 ->item(L"width", L"The new width, 1 = 1x the screen width, 0.5 = half the screen width. Higher and lower values allowed. Negative values flips the layer.")
1581 ->item(L"height", L"The new height, 1 = 1x the screen height, 0.5 = half the screen height. Higher and lower values allowed. Negative values flips the layer.");
1582 sink.para()->text(L"Examples:");
1583 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1585 L">> MIXER 1-0 CLIP\n"
1586 L"<< 201 MIXER OK\n"
1587 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1590 std::wstring mixer_clip_command(command_context& ctx)
1592 if (ctx.parameters.empty())
1594 auto transform = get_current_transform(ctx).image_transform;
1595 auto translation = transform.clip_translation;
1596 auto scale = transform.clip_scale;
1598 return L"201 MIXER OK\r\n"
1599 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1600 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1601 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1602 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1605 transforms_applier transforms(ctx);
1606 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1607 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1608 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1609 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1610 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1611 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1613 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1615 transform.image_transform.clip_translation[0] = x;
1616 transform.image_transform.clip_translation[1] = y;
1617 transform.image_transform.clip_scale[0] = x_s;
1618 transform.image_transform.clip_scale[1] = y_s;
1620 }, duration, tween));
1623 return L"202 MIXER OK\r\n";
1626 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1628 sink.short_description(L"Change the anchor point of a layer.");
1629 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1630 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1632 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1633 ->text(L" will be done from.");
1635 ->item(L"x", L"The x anchor point, 0 = left edge of layer, 0.5 = middle of layer, 1.0 = right edge of layer. Higher and lower values allowed.")
1636 ->item(L"y", L"The y anchor point, 0 = top edge of layer, 0.5 = middle of layer, 1.0 = bottom edge of layer. Higher and lower values allowed.");
1637 sink.para()->text(L"Examples:");
1638 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1640 L">> MIXER 1-10 ANCHOR\n"
1641 L"<< 201 MIXER OK\n"
1642 L"<< 0.5 0.6", L"gets the anchor point");
1645 std::wstring mixer_anchor_command(command_context& ctx)
1647 if (ctx.parameters.empty())
1649 auto transform = get_current_transform(ctx).image_transform;
1650 auto anchor = transform.anchor;
1651 return L"201 MIXER OK\r\n"
1652 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1653 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1656 transforms_applier transforms(ctx);
1657 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1658 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1659 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1660 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1662 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1664 transform.image_transform.anchor[0] = x;
1665 transform.image_transform.anchor[1] = y;
1667 }, duration, tween));
1670 return L"202 MIXER OK\r\n";
1673 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1675 sink.short_description(L"Crop a layer.");
1676 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CROP {[left-edge:float] [top-edge:float] [right-edge:float] [bottom-edge:float]" + ANIMATION_SYNTAX);
1678 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1679 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1680 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1682 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1683 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1684 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1685 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1686 sink.para()->text(L"Examples:");
1687 sink.example(L">> MIXER 1-0 CROP 0.25 0.25 0.75 0.75 25 easeinsine", L"leaving a 25% crop around the edges");
1689 L">> MIXER 1-0 CROP\n"
1690 L"<< 201 MIXER OK\n"
1691 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1694 std::wstring mixer_crop_command(command_context& ctx)
1696 if (ctx.parameters.empty())
1698 auto crop = get_current_transform(ctx).image_transform.crop;
1699 return L"201 MIXER OK\r\n"
1700 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1701 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1702 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1703 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1706 transforms_applier transforms(ctx);
1707 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1708 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1709 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1710 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1711 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1712 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1714 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1716 transform.image_transform.crop.ul[0] = ul_x;
1717 transform.image_transform.crop.ul[1] = ul_y;
1718 transform.image_transform.crop.lr[0] = lr_x;
1719 transform.image_transform.crop.lr[1] = lr_y;
1721 }, duration, tween));
1724 return L"202 MIXER OK\r\n";
1727 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1729 sink.short_description(L"Rotate a layer.");
1730 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1732 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1733 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1734 sink.para()->text(L"Examples:");
1735 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1737 L">> MIXER 1-0 ROTATION\n"
1738 L"<< 201 MIXER OK\n"
1739 L"<< 45", L"to retrieve the current angle");
1742 std::wstring mixer_rotation_command(command_context& ctx)
1744 static const double PI = 3.141592653589793;
1746 return single_double_animatable_mixer_command(
1748 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1749 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1752 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1754 sink.short_description(L"Adjust the perspective transform of a layer.");
1755 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} PERSPECTIVE {[top-left-x:float] [top-left-y:float] [top-right-x:float] [top-right-y:float] [bottom-right-x:float] [bottom-right-y:float] [bottom-left-x:float] [bottom-left-y:float]" + ANIMATION_SYNTAX);
1757 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1759 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1760 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1761 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1762 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1763 sink.para()->text(L"Examples:");
1764 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1766 L">> MIXER 1-10 PERSPECTIVE\n"
1767 L"<< 201 MIXER OK\n"
1768 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1771 std::wstring mixer_perspective_command(command_context& ctx)
1773 if (ctx.parameters.empty())
1775 auto perspective = get_current_transform(ctx).image_transform.perspective;
1778 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1779 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1780 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1781 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1782 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1783 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1784 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1785 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1788 transforms_applier transforms(ctx);
1789 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1790 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1791 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1792 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1793 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1794 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1795 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1796 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1797 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1798 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1800 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1802 transform.image_transform.perspective.ul[0] = ul_x;
1803 transform.image_transform.perspective.ul[1] = ul_y;
1804 transform.image_transform.perspective.ur[0] = ur_x;
1805 transform.image_transform.perspective.ur[1] = ur_y;
1806 transform.image_transform.perspective.lr[0] = lr_x;
1807 transform.image_transform.perspective.lr[1] = lr_y;
1808 transform.image_transform.perspective.ll[0] = ll_x;
1809 transform.image_transform.perspective.ll[1] = ll_y;
1811 }, duration, tween));
1814 return L"202 MIXER OK\r\n";
1817 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1819 sink.short_description(L"Enable or disable mipmapping for a layer.");
1820 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1822 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1823 ->text(L"If no argument is given the current state is returned.");
1824 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1825 sink.para()->text(L"Examples:");
1826 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1828 L">> MIXER 1-10 MIPMAP\n"
1829 L"<< 201 MIXER OK\n"
1830 L"<< 1", L"for getting the current state");
1833 std::wstring mixer_mipmap_command(command_context& ctx)
1835 if (ctx.parameters.empty())
1836 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1838 transforms_applier transforms(ctx);
1839 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1840 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1842 transform.image_transform.use_mipmap = value;
1847 return L"202 MIXER OK\r\n";
1850 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1852 sink.short_description(L"Change the volume of a layer.");
1853 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1854 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1855 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1856 sink.para()->text(L"Examples:");
1857 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1858 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1860 L">> MIXER 1-0 VOLUME\n"
1861 L"<< 201 MIXER OK\n"
1862 L"<< 0.8", L"to retrieve the current volume");
1865 std::wstring mixer_volume_command(command_context& ctx)
1867 return single_double_animatable_mixer_command(
1869 [](const frame_transform& t) { return t.audio_transform.volume; },
1870 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1873 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1875 sink.short_description(L"Change the volume of an entire channel.");
1876 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1877 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1878 sink.para()->text(L"Examples:");
1879 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1880 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1881 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1884 std::wstring mixer_mastervolume_command(command_context& ctx)
1886 if (ctx.parameters.empty())
1888 auto volume = ctx.channel.channel->mixer().get_master_volume();
1889 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1892 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1893 ctx.channel.channel->mixer().set_master_volume(master_volume);
1895 return L"202 MIXER OK\r\n";
1898 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1900 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1901 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1902 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1903 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1904 sink.para()->text(L"Examples:");
1905 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1906 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1908 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1909 L"<< 201 MIXER OK\n"
1913 std::wstring mixer_straight_alpha_command(command_context& ctx)
1915 if (ctx.parameters.empty())
1917 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1918 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1921 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1922 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1924 return L"202 MIXER OK\r\n";
1927 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1929 sink.short_description(L"Create a grid of video layers.");
1930 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1932 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1933 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1934 sink.para()->text(L"Examples:");
1935 sink.example(L">> MIXER 1 GRID 2");
1938 std::wstring mixer_grid_command(command_context& ctx)
1940 transforms_applier transforms(ctx);
1941 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1942 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1943 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1944 double delta = 1.0 / static_cast<double>(n);
1945 for (int x = 0; x < n; ++x)
1947 for (int y = 0; y < n; ++y)
1949 int index = x + y*n + 1;
1950 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1952 transform.image_transform.fill_translation[0] = x*delta;
1953 transform.image_transform.fill_translation[1] = y*delta;
1954 transform.image_transform.fill_scale[0] = delta;
1955 transform.image_transform.fill_scale[1] = delta;
1956 transform.image_transform.clip_translation[0] = x*delta;
1957 transform.image_transform.clip_translation[1] = y*delta;
1958 transform.image_transform.clip_scale[0] = delta;
1959 transform.image_transform.clip_scale[1] = delta;
1961 }, duration, tween));
1966 return L"202 MIXER OK\r\n";
1969 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1971 sink.short_description(L"Commit all deferred mixer transforms.");
1972 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1973 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1974 sink.para()->text(L"Examples:");
1976 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1977 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1978 L">> MIXER 1 COMMIT");
1981 std::wstring mixer_commit_command(command_context& ctx)
1983 transforms_applier transforms(ctx);
1984 transforms.commit_deferred();
1986 return L"202 MIXER OK\r\n";
1989 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1991 sink.short_description(L"Clear all transformations on a channel or layer.");
1992 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1993 sink.para()->text(L"Clears all transformations on a channel or layer.");
1994 sink.para()->text(L"Examples:");
1995 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1996 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1999 std::wstring mixer_clear_command(command_context& ctx)
2001 int layer = ctx.layer_id;
2004 ctx.channel.channel->stage().clear_transforms();
2006 ctx.channel.channel->stage().clear_transforms(layer);
2008 return L"202 MIXER OK\r\n";
2011 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2013 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2014 sink.syntax(L"CHANNEL_GRID");
2015 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2017 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2018 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2021 std::wstring channel_grid_command(command_context& ctx)
2024 auto self = ctx.channels.back();
2026 core::diagnostics::scoped_call_context save;
2027 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2029 std::vector<std::wstring> params;
2030 params.push_back(L"SCREEN");
2031 params.push_back(L"0");
2032 params.push_back(L"NAME");
2033 params.push_back(L"Channel Grid Window");
2034 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
2036 self.channel->output().add(screen);
2038 for (auto& channel : ctx.channels)
2040 if (channel.channel != self.channel)
2042 core::diagnostics::call_context::for_thread().layer = index;
2043 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2044 self.channel->stage().load(index, producer, false);
2045 self.channel->stage().play(index);
2050 auto num_channels = ctx.channels.size() - 1;
2051 int square_side_length = std::ceil(std::sqrt(num_channels));
2053 ctx.channel_index = self.channel->index();
2055 ctx.parameters.clear();
2056 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2057 mixer_grid_command(ctx);
2059 return L"202 CHANNEL_GRID OK\r\n";
2062 // Thumbnail Commands
2064 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2066 sink.short_description(L"List all thumbnails.");
2067 sink.syntax(L"THUMBNAIL LIST");
2068 sink.para()->text(L"Lists all thumbnails.");
2069 sink.para()->text(L"Examples:");
2071 L">> THUMBNAIL LIST\n"
2072 L"<< 200 THUMBNAIL LIST OK\n"
2073 L"<< \"AMB\" 20130301T124409 1149\n"
2074 L"<< \"foo/bar\" 20130523T234001 244");
2077 std::wstring thumbnail_list_command(command_context& ctx)
2079 std::wstringstream replyString;
2080 replyString << L"200 THUMBNAIL LIST OK\r\n";
2082 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2084 if (boost::filesystem::is_regular_file(itr->path()))
2086 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2089 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
2091 auto str = relativePath.replace_extension(L"").generic_wstring();
2092 if (str[0] == '\\' || str[0] == '/')
2093 str = std::wstring(str.begin() + 1, str.end());
2095 auto mtime = boost::filesystem::last_write_time(itr->path());
2096 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2097 auto file_size = boost::filesystem::file_size(itr->path());
2099 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2103 replyString << L"\r\n";
2105 return boost::to_upper_copy(replyString.str());
2108 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2110 sink.short_description(L"Retrieve a thumbnail.");
2111 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2112 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2113 sink.para()->text(L"Examples:");
2115 L">> THUMBNAIL RETRIEVE foo/bar\n"
2116 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2117 L"<< ...base64 data...");
2120 std::wstring thumbnail_retrieve_command(command_context& ctx)
2122 std::wstring filename = env::thumbnails_folder();
2123 filename.append(ctx.parameters.at(0));
2124 filename.append(L".png");
2126 std::wstring file_contents;
2128 auto found_file = find_case_insensitive(filename);
2131 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2133 if (file_contents.empty())
2134 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2136 std::wstringstream reply;
2138 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2139 reply << file_contents;
2144 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2146 sink.short_description(L"Regenerate a thumbnail.");
2147 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2148 sink.para()->text(L"Regenerates a thumbnail.");
2151 std::wstring thumbnail_generate_command(command_context& ctx)
2155 ctx.thumb_gen->generate(ctx.parameters.at(0));
2156 return L"202 THUMBNAIL GENERATE OK\r\n";
2159 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2162 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2164 sink.short_description(L"Regenerate all thumbnails.");
2165 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2166 sink.para()->text(L"Regenerates all thumbnails.");
2169 std::wstring thumbnail_generateall_command(command_context& ctx)
2173 ctx.thumb_gen->generate_all();
2174 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2177 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2182 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2184 sink.short_description(L"Get information about a media file.");
2185 sink.syntax(L"CINF [filename:string]");
2186 sink.para()->text(L"Returns information about a media file.");
2189 std::wstring cinf_command(command_context& ctx)
2192 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2194 auto path = itr->path();
2195 auto file = path.replace_extension(L"").filename().wstring();
2196 if (boost::iequals(file, ctx.parameters.at(0)))
2197 info += MediaInfo(itr->path(), ctx.media_info_repo);
2201 CASPAR_THROW_EXCEPTION(file_not_found());
2203 std::wstringstream replyString;
2204 replyString << L"200 CINF OK\r\n";
2205 replyString << info << "\r\n";
2207 return replyString.str();
2210 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2212 sink.short_description(L"List all media files.");
2213 sink.syntax(L"CLS");
2215 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2216 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2219 std::wstring cls_command(command_context& ctx)
2221 std::wstringstream replyString;
2222 replyString << L"200 CLS OK\r\n";
2223 replyString << ListMedia(ctx.media_info_repo);
2224 replyString << L"\r\n";
2225 return boost::to_upper_copy(replyString.str());
2228 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2230 sink.short_description(L"List all fonts.");
2231 sink.syntax(L"FLS");
2233 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2234 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2235 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2238 std::wstring fls_command(command_context& ctx)
2240 std::wstringstream replyString;
2241 replyString << L"200 FLS OK\r\n";
2243 for (auto& font : core::text::list_fonts())
2244 replyString << L"\"" << font.first << L"\" \"" << font.second << L"\"\r\n";
2246 replyString << L"\r\n";
2248 return replyString.str();
2251 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2253 sink.short_description(L"List all templates.");
2254 sink.syntax(L"TLS");
2256 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2257 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2260 std::wstring tls_command(command_context& ctx)
2262 std::wstringstream replyString;
2263 replyString << L"200 TLS OK\r\n";
2265 replyString << ListTemplates(ctx.cg_registry);
2266 replyString << L"\r\n";
2268 return replyString.str();
2271 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2273 sink.short_description(L"Get version information.");
2274 sink.syntax(L"VERSION {[component:string]}");
2275 sink.para()->text(L"Returns the version of specified component.");
2276 sink.para()->text(L"Examples:");
2279 L"<< 201 VERSION OK\n"
2280 L"<< 2.1.0.f207a33 STABLE");
2282 L">> VERSION SERVER\n"
2283 L"<< 201 VERSION OK\n"
2284 L"<< 2.1.0.f207a33 STABLE");
2286 L">> VERSION FLASH\n"
2287 L"<< 201 VERSION OK\n"
2291 std::wstring version_command(command_context& ctx)
2293 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2295 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2297 return L"201 VERSION OK\r\n" + version + L"\r\n";
2300 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2303 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2305 sink.short_description(L"Get a list of the available channels.");
2306 sink.syntax(L"INFO");
2307 sink.para()->text(L"Retrieves a list of the available channels.");
2311 L"<< 1 720p5000 PLAYING\n"
2312 L"<< 2 PAL PLAYING");
2315 std::wstring info_command(command_context& ctx)
2317 std::wstringstream replyString;
2318 // This is needed for backwards compatibility with old clients
2319 replyString << L"200 INFO OK\r\n";
2320 for (size_t n = 0; n < ctx.channels.size(); ++n)
2321 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2322 replyString << L"\r\n";
2323 return replyString.str();
2326 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2328 std::wstringstream replyString;
2330 if (command.empty())
2331 replyString << L"201 INFO OK\r\n";
2333 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2335 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2336 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2337 replyString << L"\r\n";
2338 return replyString.str();
2341 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2343 sink.short_description(L"Get information about a channel or a layer.");
2344 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2345 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2346 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2349 std::wstring info_channel_command(command_context& ctx)
2351 boost::property_tree::wptree info;
2352 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2354 if (layer == std::numeric_limits<int>::min())
2356 info.add_child(L"channel", ctx.channel.channel->info())
2357 .add(L"index", ctx.channel_index);
2361 if (ctx.parameters.size() >= 1)
2363 if (boost::iequals(ctx.parameters.at(0), L"B"))
2364 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2366 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2370 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2374 return create_info_xml_reply(info);
2377 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2379 sink.short_description(L"Get information about a template.");
2380 sink.syntax(L"INFO TEMPLATE [template:string]");
2381 sink.para()->text(L"Gets information about the specified template.");
2384 std::wstring info_template_command(command_context& ctx)
2386 auto filename = ctx.parameters.at(0);
2388 std::wstringstream str;
2389 str << u16(ctx.cg_registry->read_meta_info(filename));
2390 boost::property_tree::wptree info;
2391 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2393 return create_info_xml_reply(info, L"TEMPLATE");
2396 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2398 sink.short_description(L"Get the contents of the configuration used.");
2399 sink.syntax(L"INFO CONFIG");
2400 sink.para()->text(L"Gets the contents of the configuration used.");
2403 std::wstring info_config_command(command_context& ctx)
2405 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2408 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2410 sink.short_description(L"Get information about the paths used.");
2411 sink.syntax(L"INFO PATHS");
2412 sink.para()->text(L"Gets information about the paths used.");
2415 std::wstring info_paths_command(command_context& ctx)
2417 boost::property_tree::wptree info;
2418 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2419 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2421 return create_info_xml_reply(info, L"PATHS");
2424 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2426 sink.short_description(L"Get system information.");
2427 sink.syntax(L"INFO SYSTEM");
2428 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2431 std::wstring info_system_command(command_context& ctx)
2433 boost::property_tree::wptree info;
2435 info.add(L"system.name", caspar::system_product_name());
2436 info.add(L"system.os.description", caspar::os_description());
2437 info.add(L"system.cpu", caspar::cpu_info());
2439 ctx.system_info_repo->fill_information(info);
2441 return create_info_xml_reply(info, L"SYSTEM");
2444 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2446 sink.short_description(L"Get detailed information about all channels.");
2447 sink.syntax(L"INFO SERVER");
2448 sink.para()->text(L"Gets detailed information about all channels.");
2451 std::wstring info_server_command(command_context& ctx)
2453 boost::property_tree::wptree info;
2456 for (auto& channel : ctx.channels)
2457 info.add_child(L"channels.channel", channel.channel->info())
2458 .add(L"index", ++index);
2460 return create_info_xml_reply(info, L"SERVER");
2463 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2465 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2466 sink.syntax(L"INFO QUEUES");
2467 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2470 std::wstring info_queues_command(command_context& ctx)
2472 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2475 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2477 sink.short_description(L"Lists all known threads in the server.");
2478 sink.syntax(L"INFO THREADS");
2479 sink.para()->text(L"Lists all known threads in the server.");
2482 std::wstring info_threads_command(command_context& ctx)
2484 std::wstringstream replyString;
2485 replyString << L"200 INFO THREADS OK\r\n";
2487 for (auto& thread : get_thread_infos())
2489 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2492 replyString << L"\r\n";
2493 return replyString.str();
2496 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2498 sink.short_description(L"Get the current delay on a channel or a layer.");
2499 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2500 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2503 std::wstring info_delay_command(command_context& ctx)
2505 boost::property_tree::wptree info;
2506 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2508 if (layer == std::numeric_limits<int>::min())
2509 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2511 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2512 .add(L"index", layer);
2514 return create_info_xml_reply(info, L"DELAY");
2517 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2519 sink.short_description(L"Open the diagnostics window.");
2520 sink.syntax(L"DIAG");
2521 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2524 std::wstring diag_command(command_context& ctx)
2526 core::diagnostics::osd::show_graphs(true);
2528 return L"202 DIAG OK\r\n";
2531 static const int WIDTH = 80;
2533 struct max_width_sink : public core::help_sink
2535 std::size_t max_width = 0;
2537 void begin_item(const std::wstring& name) override
2539 max_width = std::max(name.length(), max_width);
2543 struct short_description_sink : public core::help_sink
2546 std::wstringstream& out;
2548 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2550 void begin_item(const std::wstring& name) override
2552 out << std::left << std::setw(width + 1) << name;
2555 void short_description(const std::wstring& short_description) override
2557 out << short_description << L"\r\n";
2561 struct simple_paragraph_builder : core::paragraph_builder
2563 std::wostringstream out;
2564 std::wstringstream& commit_to;
2566 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2567 ~simple_paragraph_builder()
2569 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2571 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2573 out << std::move(text);
2574 return shared_from_this();
2576 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2577 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2578 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2581 struct simple_definition_list_builder : core::definition_list_builder
2583 std::wstringstream& out;
2585 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2586 ~simple_definition_list_builder()
2591 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2593 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2594 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2595 return shared_from_this();
2599 struct long_description_sink : public core::help_sink
2601 std::wstringstream& out;
2603 long_description_sink(std::wstringstream& out) : out(out) { }
2605 void syntax(const std::wstring& syntax) override
2607 out << L"Syntax:\n";
2608 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2611 spl::shared_ptr<core::paragraph_builder> para() override
2613 return spl::make_shared<simple_paragraph_builder>(out);
2616 spl::shared_ptr<core::definition_list_builder> definitions() override
2618 return spl::make_shared<simple_definition_list_builder>(out);
2621 void example(const std::wstring& code, const std::wstring& caption) override
2623 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2625 if (!caption.empty())
2626 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2631 void begin_item(const std::wstring& name) override
2633 out << name << L"\n\n";
2637 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2639 std::wstringstream result;
2640 result << L"200 " << help_command << L" OK\r\n";
2641 max_width_sink width;
2642 ctx.help_repo->help(tags, width);
2643 short_description_sink sink(width.max_width, result);
2644 sink.width = width.max_width;
2645 ctx.help_repo->help(tags, sink);
2647 return result.str();
2650 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2652 std::wstringstream result;
2653 result << L"201 " << help_command << L" OK\r\n";
2654 auto joined = boost::join(ctx.parameters, L" ");
2655 long_description_sink sink(result);
2656 ctx.help_repo->help(tags, joined, sink);
2658 return result.str();
2661 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2663 sink.short_description(L"Show online help for AMCP commands.");
2664 sink.syntax(LR"(HELP {[command:string]})");
2665 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2666 sink.example(L">> HELP", L"Shows a list of commands.");
2667 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2670 std::wstring help_command(command_context& ctx)
2672 if (ctx.parameters.size() == 0)
2673 return create_help_list(L"HELP", ctx, { L"AMCP" });
2675 return create_help_details(L"HELP", ctx, { L"AMCP" });
2678 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2680 sink.short_description(L"Show online help for producers.");
2681 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2682 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2683 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2684 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2687 std::wstring help_producer_command(command_context& ctx)
2689 if (ctx.parameters.size() == 0)
2690 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2692 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2695 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2697 sink.short_description(L"Show online help for consumers.");
2698 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2699 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2700 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2701 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2704 std::wstring help_consumer_command(command_context& ctx)
2706 if (ctx.parameters.size() == 0)
2707 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2709 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2712 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2714 sink.short_description(L"Disconnect the session.");
2715 sink.syntax(L"BYE");
2717 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2718 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2721 std::wstring bye_command(command_context& ctx)
2723 ctx.client->disconnect();
2727 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2729 sink.short_description(L"Shutdown the server.");
2730 sink.syntax(L"KILL");
2731 sink.para()->text(L"Shuts the server down.");
2734 std::wstring kill_command(command_context& ctx)
2736 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2737 return L"202 KILL OK\r\n";
2740 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2742 sink.short_description(L"Shutdown the server with restart exit code.");
2743 sink.syntax(L"RESTART");
2745 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2746 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2749 std::wstring restart_command(command_context& ctx)
2751 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2752 return L"202 RESTART OK\r\n";
2755 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2757 sink.short_description(L"Lock or unlock access to a channel.");
2758 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2759 sink.para()->text(L"Allows for exclusive access to a channel.");
2760 sink.para()->text(L"Examples:");
2761 sink.example(L"LOCK 1 ACQUIRE secret");
2762 sink.example(L"LOCK 1 RELEASE");
2763 sink.example(L"LOCK 1 CLEAR");
2766 std::wstring lock_command(command_context& ctx)
2768 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2769 auto lock = ctx.channels.at(channel_index).lock;
2770 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2772 if (command == L"ACQUIRE")
2774 std::wstring lock_phrase = ctx.parameters.at(2);
2776 //TODO: read options
2778 //just lock one channel
2779 if (!lock->try_lock(lock_phrase, ctx.client))
2780 return L"503 LOCK ACQUIRE FAILED\r\n";
2782 return L"202 LOCK ACQUIRE OK\r\n";
2784 else if (command == L"RELEASE")
2786 lock->release_lock(ctx.client);
2787 return L"202 LOCK RELEASE OK\r\n";
2789 else if (command == L"CLEAR")
2791 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2792 std::wstring client_override_phrase;
2794 if (!override_phrase.empty())
2795 client_override_phrase = ctx.parameters.at(2);
2797 //just clear one channel
2798 if (client_override_phrase != override_phrase)
2799 return L"503 LOCK CLEAR FAILED\r\n";
2801 lock->clear_locks();
2803 return L"202 LOCK CLEAR OK\r\n";
2806 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2809 void register_commands(amcp_command_repository& repo)
2811 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2812 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2813 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2814 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2815 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2816 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2817 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2818 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2819 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2820 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2821 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2822 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2823 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2824 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2825 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2827 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2828 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2829 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2830 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2832 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2833 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2834 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2835 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2836 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2837 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2838 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2839 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2840 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2842 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2843 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2844 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2845 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2846 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2847 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2848 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2849 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2850 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2851 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2852 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2853 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2854 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2855 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2856 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2857 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2858 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2859 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2860 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2861 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2862 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2863 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2865 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2866 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2867 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2868 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2870 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2871 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2872 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
2873 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2874 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2875 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2876 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2877 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2878 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2879 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2880 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2881 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2882 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2883 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2884 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2885 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2886 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2887 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2888 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2889 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2890 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2891 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2895 }} //namespace caspar