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>
41 #include <common/filesystem.h>
43 #include <core/producer/cg_proxy.h>
44 #include <core/producer/frame_producer.h>
45 #include <core/help/help_repository.h>
46 #include <core/help/help_sink.h>
47 #include <core/help/util.h>
48 #include <core/video_format.h>
49 #include <core/producer/transition/transition_producer.h>
50 #include <core/frame/audio_channel_layout.h>
51 #include <core/frame/frame_transform.h>
52 #include <core/producer/text/text_producer.h>
53 #include <core/producer/stage.h>
54 #include <core/producer/layer.h>
55 #include <core/mixer/mixer.h>
56 #include <core/consumer/output.h>
57 #include <core/thumbnail_generator.h>
58 #include <core/producer/media_info/media_info.h>
59 #include <core/producer/media_info/media_info_repository.h>
60 #include <core/diagnostics/call_context.h>
61 #include <core/diagnostics/osd_graph.h>
62 #include <core/system_info_provider.h>
71 #include <boost/date_time/posix_time/posix_time.hpp>
72 #include <boost/lexical_cast.hpp>
73 #include <boost/algorithm/string.hpp>
74 #include <boost/filesystem.hpp>
75 #include <boost/filesystem/fstream.hpp>
76 #include <boost/regex.hpp>
77 #include <boost/property_tree/xml_parser.hpp>
78 #include <boost/locale.hpp>
79 #include <boost/range/adaptor/transformed.hpp>
80 #include <boost/range/algorithm/copy.hpp>
81 #include <boost/archive/iterators/base64_from_binary.hpp>
82 #include <boost/archive/iterators/insert_linebreaks.hpp>
83 #include <boost/archive/iterators/transform_width.hpp>
85 #include <tbb/concurrent_unordered_map.h>
89 102 [action] Information that [action] has happened
90 101 [action] Information that [action] has happened plus one row of data
92 202 [command] OK [command] has been executed
93 201 [command] OK [command] has been executed, plus one row of data
94 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
96 400 ERROR the command could not be understood
97 401 [command] ERROR invalid/missing channel
98 402 [command] ERROR parameter missing
99 403 [command] ERROR invalid parameter
100 404 [command] ERROR file not found
102 500 FAILED internal error
103 501 [command] FAILED internal error
104 502 [command] FAILED could not read file
105 503 [command] FAILED access denied
107 600 [command] FAILED [command] not implemented
110 namespace caspar { namespace protocol { namespace amcp {
112 using namespace core;
114 std::wstring read_file_base64(const boost::filesystem::path& file)
116 using namespace boost::archive::iterators;
118 boost::filesystem::ifstream filestream(file, std::ios::binary);
123 auto length = boost::filesystem::file_size(file);
124 std::vector<char> bytes;
125 bytes.resize(length);
126 filestream.read(bytes.data(), length);
128 std::string result(to_base64(bytes.data(), length));
129 return std::wstring(result.begin(), result.end());
132 std::wstring read_utf8_file(const boost::filesystem::path& file)
134 std::wstringstream result;
135 boost::filesystem::wifstream filestream(file);
142 result << filestream.rdbuf();
148 std::wstring read_latin1_file(const boost::filesystem::path& file)
150 boost::locale::generator gen;
151 gen.locale_cache_enabled(true);
152 gen.categories(boost::locale::codepage_facet);
154 std::stringstream result_stream;
155 boost::filesystem::ifstream filestream(file);
156 filestream.imbue(gen("en_US.ISO8859-1"));
161 result_stream << filestream.rdbuf();
164 std::string result = result_stream.str();
165 std::wstring widened_result;
167 // The first 255 codepoints in unicode is the same as in latin1
169 result | boost::adaptors::transformed(
170 [](char c) { return static_cast<unsigned char>(c); }),
171 std::back_inserter(widened_result));
173 return widened_result;
176 std::wstring read_file(const boost::filesystem::path& file)
178 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
180 if (!boost::filesystem::exists(file))
185 if (boost::filesystem::file_size(file) >= 3)
187 boost::filesystem::ifstream bom_stream(file);
190 bom_stream.read(header, 3);
193 if (std::memcmp(BOM, header, 3) == 0)
194 return read_utf8_file(file);
197 return read_latin1_file(file);
200 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
202 if (!boost::filesystem::is_regular_file(path))
205 auto media_info = media_info_repo->get(path.wstring());
210 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
212 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
213 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
214 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
216 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
217 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
218 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
220 auto relativePath = get_relative_without_extension(path, env::media_folder());
221 auto str = relativePath.generic_wstring();
223 if (str[0] == '\\' || str[0] == '/')
224 str = std::wstring(str.begin() + 1, str.end());
226 return std::wstring()
228 + L"\" " + media_info->clip_type +
230 + L" " + writeTimeWStr +
231 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
232 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
236 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
238 std::wstringstream replyString;
239 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
240 replyString << MediaInfo(itr->path(), media_info_repo);
242 return boost::to_upper_copy(replyString.str());
245 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
247 std::wstringstream replyString;
249 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
251 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
253 auto relativePath = get_relative_without_extension(itr->path(), env::template_folder());
255 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
256 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
257 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
259 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
260 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
262 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
264 auto dir = relativePath.parent_path();
265 auto file = boost::to_upper_copy(relativePath.filename().wstring());
266 relativePath = dir / file;
268 auto str = relativePath.generic_wstring();
269 boost::trim_if(str, boost::is_any_of("\\/"));
271 auto template_type = cg_registry->get_cg_producer_name(str);
273 replyString << L"\"" << str
274 << L"\" " << sizeWStr
275 << L" " << writeTimeWStr
276 << L" " << template_type
280 return replyString.str();
283 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
285 return core::frame_producer_dependencies(
286 channel->frame_factory(),
287 cpplinq::from(ctx.channels)
288 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
290 channel->video_format_desc(),
291 ctx.producer_registry);
296 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
298 sink.short_description(L"Load a media file or resource in the background.");
299 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]})");
301 ->text(L"Loads a producer in the background and prepares it for playout. ")
302 ->text(L"If no layer is specified the default layer index will be used.");
304 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
305 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
307 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
308 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
310 ->code(L"loop")->text(L" will cause the clip to loop.");
312 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
314 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
316 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
317 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
318 sink.para()->text(L"Examples:");
319 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
320 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
321 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
322 sink.example(L">> LOADBG 1-0 MY_FILE");
324 L">> PLAY 1-1 MY_FILE\n"
325 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
326 L"To automatically fade out a layer after a video file has been played to the end");
328 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
330 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
331 ->code(L"filter")->text(L" command.");
334 std::wstring loadbg_command(command_context& ctx)
336 transition_info transitionInfo;
340 std::wstring message;
341 for (size_t n = 0; n < ctx.parameters.size(); ++n)
342 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
344 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)?.*)");
346 if (boost::regex_match(message, what, expr))
348 auto transition = what["TRANSITION"].str();
349 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
350 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
351 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
352 transitionInfo.tweener = tween;
354 if (transition == L"CUT")
355 transitionInfo.type = transition_type::cut;
356 else if (transition == L"MIX")
357 transitionInfo.type = transition_type::mix;
358 else if (transition == L"PUSH")
359 transitionInfo.type = transition_type::push;
360 else if (transition == L"SLIDE")
361 transitionInfo.type = transition_type::slide;
362 else if (transition == L"WIPE")
363 transitionInfo.type = transition_type::wipe;
365 if (direction == L"FROMLEFT")
366 transitionInfo.direction = transition_direction::from_left;
367 else if (direction == L"FROMRIGHT")
368 transitionInfo.direction = transition_direction::from_right;
369 else if (direction == L"LEFT")
370 transitionInfo.direction = transition_direction::from_right;
371 else if (direction == L"RIGHT")
372 transitionInfo.direction = transition_direction::from_left;
375 //Perform loading of the clip
376 core::diagnostics::scoped_call_context save;
377 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
378 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
380 auto channel = ctx.channel.channel;
381 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
383 if (pFP == frame_producer::empty())
384 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
386 bool auto_play = contains_param(L"AUTO", ctx.parameters);
388 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
390 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
392 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
394 return L"202 LOADBG OK\r\n";
397 void load_describer(core::help_sink& sink, const core::help_repository& repo)
399 sink.short_description(L"Load a media file or resource to the foreground.");
400 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
402 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
403 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
404 sink.para()->text(L"Examples:");
405 sink.example(L">> LOAD 1 MY_FILE");
406 sink.example(L">> LOAD 1-1 MY_FILE");
407 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
410 std::wstring load_command(command_context& ctx)
412 core::diagnostics::scoped_call_context save;
413 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
414 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
415 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
416 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
418 return L"202 LOAD OK\r\n";
421 void play_describer(core::help_sink& sink, const core::help_repository& repository)
423 sink.short_description(L"Play a media file or resource.");
424 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
426 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
427 ->text(L") is prepared, it will be executed.");
429 ->text(L"If additional parameters (see ")->see(L"LOADBG")
430 ->text(L") are provided then the provided clip will first be loaded to the background.");
431 sink.para()->text(L"Examples:");
432 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
433 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
434 sink.example(L">> PLAY 1-0 MY_FILE");
435 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
438 std::wstring play_command(command_context& ctx)
440 if (!ctx.parameters.empty())
443 ctx.channel.channel->stage().play(ctx.layer_index());
445 return L"202 PLAY OK\r\n";
448 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
450 sink.short_description(L"Pause playback of a layer.");
451 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
453 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
454 ->text(L" command can be used to resume playback again.");
455 sink.para()->text(L"Examples:");
456 sink.example(L">> PAUSE 1");
457 sink.example(L">> PAUSE 1-1");
460 std::wstring pause_command(command_context& ctx)
462 ctx.channel.channel->stage().pause(ctx.layer_index());
463 return L"202 PAUSE OK\r\n";
466 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
468 sink.short_description(L"Resume playback of a layer.");
469 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
471 ->text(L"Resumes playback of a foreground clip previously paused with the ")
472 ->see(L"PAUSE")->text(L" command.");
473 sink.para()->text(L"Examples:");
474 sink.example(L">> RESUME 1");
475 sink.example(L">> RESUME 1-1");
478 std::wstring resume_command(command_context& ctx)
480 ctx.channel.channel->stage().resume(ctx.layer_index());
481 return L"202 RESUME OK\r\n";
484 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
486 sink.short_description(L"Remove the foreground clip of a layer.");
487 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
489 ->text(L"Removes the foreground clip of the specified layer.");
490 sink.para()->text(L"Examples:");
491 sink.example(L">> STOP 1");
492 sink.example(L">> STOP 1-1");
495 std::wstring stop_command(command_context& ctx)
497 ctx.channel.channel->stage().stop(ctx.layer_index());
498 return L"202 STOP OK\r\n";
501 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
503 sink.short_description(L"Remove all clips of a layer or an entire channel.");
504 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
506 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
507 ->text(L"If no layer is specified then all layers in the specified ")
508 ->code(L"video_channel")->text(L" are cleared.");
509 sink.para()->text(L"Examples:");
510 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
511 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
514 std::wstring clear_command(command_context& ctx)
516 int index = ctx.layer_index(std::numeric_limits<int>::min());
517 if (index != std::numeric_limits<int>::min())
518 ctx.channel.channel->stage().clear(index);
520 ctx.channel.channel->stage().clear();
522 return L"202 CLEAR OK\r\n";
525 void call_describer(core::help_sink& sink, const core::help_repository& repo)
527 sink.short_description(L"Call a method on a producer.");
528 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
530 ->text(L"Calls method on the specified producer with the provided ")
531 ->code(L"param")->text(L" string.");
532 sink.para()->text(L"Examples:");
533 sink.example(L">> CALL 1 LOOP");
534 sink.example(L">> CALL 1-2 SEEK 25");
537 std::wstring call_command(command_context& ctx)
539 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
541 // TODO: because of std::async deferred timed waiting does not work
543 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
544 if (wait_res == std::future_status::timeout)
545 CASPAR_THROW_EXCEPTION(timed_out());*/
547 std::wstringstream replyString;
548 if (result.get().empty())
549 replyString << L"202 CALL OK\r\n";
551 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
553 return replyString.str();
556 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
558 sink.short_description(L"Swap layers between channels.");
559 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
561 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
562 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
563 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
564 sink.para()->text(L"Examples:");
565 sink.example(L">> SWAP 1 2");
566 sink.example(L">> SWAP 1-1 2-3");
567 sink.example(L">> SWAP 1-1 1-2");
568 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
571 std::wstring swap_command(command_context& ctx)
573 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
575 if (ctx.layer_index(-1) != -1)
577 std::vector<std::string> strs;
578 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
580 auto ch1 = ctx.channel.channel;
581 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
583 int l1 = ctx.layer_index();
584 int l2 = boost::lexical_cast<int>(strs.at(1));
586 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
590 auto ch1 = ctx.channel.channel;
591 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
592 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
595 return L"202 SWAP OK\r\n";
598 void add_describer(core::help_sink& sink, const core::help_repository& repo)
600 sink.short_description(L"Add a consumer to a video channel.");
601 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
603 ->text(L"Adds a consumer to the specified video channel. The string ")
604 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
605 ->text(L"If a successful match is found a consumer will be created and added to the ")
606 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
607 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
608 ->see(L"the CasparCG config file")->text(L".");
609 sink.para()->text(L"Examples:");
610 sink.example(L">> ADD 1 DECKLINK 1");
611 sink.example(L">> ADD 1 BLUEFISH 2");
612 sink.example(L">> ADD 1 SCREEN");
613 sink.example(L">> ADD 1 AUDIO");
614 sink.example(L">> ADD 1 IMAGE filename");
615 sink.example(L">> ADD 1 FILE filename.mov");
616 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
617 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
618 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
621 std::wstring add_command(command_context& ctx)
623 replace_placeholders(
624 L"<CLIENT_IP_ADDRESS>",
625 ctx.client->address(),
628 core::diagnostics::scoped_call_context save;
629 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
631 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
632 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
634 return L"202 ADD OK\r\n";
637 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
639 sink.short_description(L"Remove a consumer from a video channel.");
640 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
642 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
643 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
644 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
645 sink.para()->text(L"Examples:");
646 sink.example(L">> REMOVE 1 DECKLINK 1");
647 sink.example(L">> REMOVE 1 BLUEFISH 2");
648 sink.example(L">> REMOVE 1 SCREEN");
649 sink.example(L">> REMOVE 1 AUDIO");
650 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
653 std::wstring remove_command(command_context& ctx)
655 auto index = ctx.layer_index(std::numeric_limits<int>::min());
657 if (index == std::numeric_limits<int>::min())
659 replace_placeholders(
660 L"<CLIENT_IP_ADDRESS>",
661 ctx.client->address(),
664 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
667 ctx.channel.channel->output().remove(index);
669 return L"202 REMOVE OK\r\n";
672 void print_describer(core::help_sink& sink, const core::help_repository& repo)
674 sink.short_description(L"Take a snapshot of a channel.");
675 sink.syntax(L"PRINT [video_channel:int]");
677 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
678 ->code(L"media")->text(L" folder.");
679 sink.para()->text(L"Examples:");
680 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
683 std::wstring print_command(command_context& ctx)
685 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
687 return L"202 PRINT OK\r\n";
690 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
692 sink.short_description(L"Change the log level of the server.");
693 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
694 sink.para()->text(L"Changes the log level of the server.");
695 sink.para()->text(L"Examples:");
696 sink.example(L">> LOG LEVEL trace");
697 sink.example(L">> LOG LEVEL info");
700 std::wstring log_level_command(command_context& ctx)
702 log::set_log_level(ctx.parameters.at(0));
704 return L"202 LOG OK\r\n";
707 void set_describer(core::help_sink& sink, const core::help_repository& repo)
709 sink.short_description(L"Change the value of a channel variable.");
710 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
711 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
713 ->item(L"MODE", L"Changes the video format of the channel.")
714 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
715 sink.para()->text(L"Examples:");
716 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
717 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
720 std::wstring set_command(command_context& ctx)
722 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
723 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
727 auto format_desc = core::video_format_desc(value);
728 if (format_desc.format != core::video_format::invalid)
730 ctx.channel.channel->video_format_desc(format_desc);
731 return L"202 SET MODE OK\r\n";
734 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
736 else if (name == L"CHANNEL_LAYOUT")
738 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
742 ctx.channel.channel->audio_channel_layout(*channel_layout);
743 return L"202 SET CHANNEL_LAYOUT OK\r\n";
746 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid audio channel layout"));
749 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
752 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
754 sink.short_description(L"Store a dataset.");
755 sink.syntax(L"DATA STORE [name:string] [data:string]");
756 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
757 sink.para()->text(L"Directories will be created if they do not exist.");
758 sink.para()->text(L"Examples:");
759 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
760 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
763 std::wstring data_store_command(command_context& ctx)
765 std::wstring filename = env::data_folder();
766 filename.append(ctx.parameters[0]);
767 filename.append(L".ftd");
769 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
770 auto found_data_path = find_case_insensitive(data_path);
773 data_path = *found_data_path;
775 if (!boost::filesystem::exists(data_path))
776 boost::filesystem::create_directories(data_path);
778 auto found_filename = find_case_insensitive(filename);
781 filename = *found_filename; // Overwrite case insensitive.
783 boost::filesystem::wofstream datafile(filename);
785 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
787 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
788 datafile << ctx.parameters[1] << std::flush;
791 return L"202 DATA STORE OK\r\n";
794 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
796 sink.short_description(L"Retrieve a dataset.");
797 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
798 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
799 sink.para()->text(L"Examples:");
800 sink.example(L">> DATA RETRIEVE my_data");
801 sink.example(L">> DATA RETRIEVE Folder1/my_data");
804 std::wstring data_retrieve_command(command_context& ctx)
806 std::wstring filename = env::data_folder();
807 filename.append(ctx.parameters[0]);
808 filename.append(L".ftd");
810 std::wstring file_contents;
812 auto found_file = find_case_insensitive(filename);
815 file_contents = read_file(boost::filesystem::path(*found_file));
817 if (file_contents.empty())
818 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
820 std::wstringstream reply;
821 reply << L"201 DATA RETRIEVE OK\r\n";
823 std::wstringstream file_contents_stream(file_contents);
826 bool firstLine = true;
827 while (std::getline(file_contents_stream, line))
841 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
843 sink.short_description(L"List stored datasets.");
844 sink.syntax(L"DATA LIST");
845 sink.para()->text(L"Returns a list of all stored datasets.");
848 std::wstring data_list_command(command_context& ctx)
850 std::wstringstream replyString;
851 replyString << L"200 DATA LIST OK\r\n";
853 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
855 if (boost::filesystem::is_regular_file(itr->path()))
857 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
860 auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
861 auto str = relativePath.generic_wstring();
863 if (str[0] == L'\\' || str[0] == L'/')
864 str = std::wstring(str.begin() + 1, str.end());
866 replyString << str << L"\r\n";
870 replyString << L"\r\n";
872 return boost::to_upper_copy(replyString.str());
875 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
877 sink.short_description(L"Remove a stored dataset.");
878 sink.syntax(L"DATA REMOVE [name:string]");
879 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
880 sink.para()->text(L"Examples:");
881 sink.example(L">> DATA REMOVE my_data");
882 sink.example(L">> DATA REMOVE Folder1/my_data");
885 std::wstring data_remove_command(command_context& ctx)
887 std::wstring filename = env::data_folder();
888 filename.append(ctx.parameters[0]);
889 filename.append(L".ftd");
891 if (!boost::filesystem::exists(filename))
892 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
894 if (!boost::filesystem::remove(filename))
895 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
897 return L"202 DATA REMOVE OK\r\n";
900 // Template Graphics Commands
902 int get_and_validate_layer(const std::wstring& layerstring) {
903 int length = layerstring.length();
904 for (int i = 0; i < length; ++i) {
905 if (!std::isdigit(layerstring[i])) {
906 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
910 return boost::lexical_cast<int>(layerstring);
913 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
915 sink.short_description(L"Prepare a template for displaying.");
916 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
918 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
919 ->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.");
920 sink.para()->text(L"Examples:");
921 sink.example(L"CG 1 ADD 10 svtnews/info 1");
924 std::wstring cg_add_command(command_context& ctx)
926 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
928 int layer = get_and_validate_layer(ctx.parameters.at(0));
929 std::wstring label; //_parameters[2]
930 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
931 unsigned int dataIndex = 3;
933 if (ctx.parameters.at(2).length() > 1)
935 label = ctx.parameters.at(2);
938 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
939 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
942 { //read play-on-load-flag
943 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
946 const wchar_t* pDataString = 0;
947 std::wstring dataFromFile;
948 if (ctx.parameters.size() > dataIndex)
950 const std::wstring& dataString = ctx.parameters.at(dataIndex);
952 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
953 pDataString = dataString.c_str();
956 //The data is not an XML-string, it must be a filename
957 std::wstring filename = env::data_folder();
958 filename.append(dataString);
959 filename.append(L".ftd");
961 auto found_file = find_case_insensitive(filename);
965 dataFromFile = read_file(boost::filesystem::path(*found_file));
966 pDataString = dataFromFile.c_str();
971 auto filename = ctx.parameters.at(1);
972 auto proxy = ctx.cg_registry->get_or_create_proxy(
973 spl::make_shared_ptr(ctx.channel.channel),
974 get_producer_dependencies(ctx.channel.channel, ctx),
975 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
978 if (proxy == core::cg_proxy::empty())
979 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
981 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
983 return L"202 CG OK\r\n";
986 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
988 sink.short_description(L"Play and display a template.");
989 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
990 sink.para()->text(L"Plays and displays the template in the specified layer.");
991 sink.para()->text(L"Examples:");
992 sink.example(L"CG 1 PLAY 0");
995 std::wstring cg_play_command(command_context& ctx)
997 int layer = get_and_validate_layer(ctx.parameters.at(0));
998 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1000 return L"202 CG OK\r\n";
1003 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1005 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1007 if (proxy == cg_proxy::empty())
1008 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"No CG proxy running on layer"));
1013 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1015 sink.short_description(L"Stop and remove a template.");
1016 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1018 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1019 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1020 sink.para()->text(L"Examples:");
1021 sink.example(L"CG 1 STOP 0");
1024 std::wstring cg_stop_command(command_context& ctx)
1026 int layer = get_and_validate_layer(ctx.parameters.at(0));
1027 get_expected_cg_proxy(ctx)->stop(layer, 0);
1029 return L"202 CG OK\r\n";
1032 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1034 sink.short_description(LR"(Trigger a "continue" in a template.)");
1035 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1037 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1038 ->text(L"This is used to control animations that has multiple discreet steps.");
1039 sink.para()->text(L"Examples:");
1040 sink.example(L"CG 1 NEXT 0");
1043 std::wstring cg_next_command(command_context& ctx)
1045 int layer = get_and_validate_layer(ctx.parameters.at(0));
1046 get_expected_cg_proxy(ctx)->next(layer);
1048 return L"202 CG OK\r\n";
1051 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1053 sink.short_description(L"Remove a template.");
1054 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1055 sink.para()->text(L"Removes the template from the specified layer.");
1056 sink.para()->text(L"Examples:");
1057 sink.example(L"CG 1 REMOVE 0");
1060 std::wstring cg_remove_command(command_context& ctx)
1062 int layer = get_and_validate_layer(ctx.parameters.at(0));
1063 get_expected_cg_proxy(ctx)->remove(layer);
1065 return L"202 CG OK\r\n";
1068 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1070 sink.short_description(L"Remove all templates on a video layer.");
1071 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1072 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1073 sink.para()->text(L"Examples:");
1074 sink.example(L"CG 1 CLEAR");
1077 std::wstring cg_clear_command(command_context& ctx)
1079 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1081 return L"202 CG OK\r\n";
1084 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1086 sink.short_description(L"Update a template with new data.");
1087 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1088 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.");
1091 std::wstring cg_update_command(command_context& ctx)
1093 int layer = get_and_validate_layer(ctx.parameters.at(0));
1095 std::wstring dataString = ctx.parameters.at(1);
1096 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1098 //The data is not XML or Json, it must be a filename
1099 std::wstring filename = env::data_folder();
1100 filename.append(dataString);
1101 filename.append(L".ftd");
1103 dataString = read_file(boost::filesystem::path(filename));
1106 get_expected_cg_proxy(ctx)->update(layer, dataString);
1108 return L"202 CG OK\r\n";
1111 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1113 sink.short_description(L"Invoke a method/label on a template.");
1114 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1115 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1116 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1119 std::wstring cg_invoke_command(command_context& ctx)
1121 std::wstringstream replyString;
1122 replyString << L"201 CG OK\r\n";
1123 int layer = get_and_validate_layer(ctx.parameters.at(0));
1124 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1125 replyString << result << L"\r\n";
1127 return replyString.str();
1130 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1132 sink.short_description(L"Get information about a running template or the template host.");
1133 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1134 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1135 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1138 std::wstring cg_info_command(command_context& ctx)
1140 std::wstringstream replyString;
1141 replyString << L"201 CG OK\r\n";
1143 if (ctx.parameters.empty())
1145 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1146 replyString << info << L"\r\n";
1150 int layer = get_and_validate_layer(ctx.parameters.at(0));
1151 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1153 replyString << desc << L"\r\n";
1156 return replyString.str();
1161 core::frame_transform get_current_transform(command_context& ctx)
1163 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1166 template<typename Func>
1167 std::wstring reply_value(command_context& ctx, const Func& extractor)
1169 auto value = extractor(get_current_transform(ctx));
1171 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1174 class transforms_applier
1176 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1178 std::vector<stage::transform_tuple_t> transforms_;
1179 command_context& ctx_;
1182 transforms_applier(command_context& ctx)
1185 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1188 ctx.parameters.pop_back();
1191 void add(stage::transform_tuple_t&& transform)
1193 transforms_.push_back(std::move(transform));
1196 void commit_deferred()
1198 auto& transforms = deferred_transforms_[ctx_.channel_index];
1199 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1207 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1208 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1211 ctx_.channel.channel->stage().apply_transforms(transforms_);
1214 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1216 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1218 sink.short_description(L"Let a layer act as alpha for the one obove.");
1219 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1221 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1222 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1223 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1224 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1225 ->text(L"instead it will be used as the key for the layer above.");
1226 sink.para()->text(L"Examples:");
1227 sink.example(L">> MIXER 1-0 KEYER 1");
1229 L">> MIXER 1-0 KEYER\n"
1230 L"<< 201 MIXER OK\n"
1231 L"<< 1", L"to retrieve the current state");
1234 std::wstring mixer_keyer_command(command_context& ctx)
1236 if (ctx.parameters.empty())
1237 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1239 transforms_applier transforms(ctx);
1240 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1241 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1243 transform.image_transform.is_key = value;
1248 return L"202 MIXER OK\r\n";
1251 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1253 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1255 sink.short_description(L"Enable chroma keying on a layer.");
1256 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1258 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1259 sink.para()->text(L"Examples:");
1260 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1261 sink.example(L">> MIXER 1-1 CHROMA none");
1263 L">> MIXER 1-1 BLEND\n"
1264 L"<< 201 MIXER OK\n"
1265 L"<< SCREEN", L"for getting the current blend mode");
1268 std::wstring mixer_chroma_command(command_context& ctx)
1270 if (ctx.parameters.empty())
1272 auto chroma = get_current_transform(ctx).image_transform.chroma;
1273 return L"201 MIXER OK\r\n"
1274 + core::get_chroma_mode(chroma.key) + L" "
1275 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1276 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1277 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1280 transforms_applier transforms(ctx);
1281 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1282 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1284 core::chroma chroma;
1285 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1287 if (chroma.key != core::chroma::type::none)
1289 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1290 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1291 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1294 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1296 transform.image_transform.chroma = chroma;
1298 }, duration, tween));
1301 return L"202 MIXER OK\r\n";
1304 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1306 sink.short_description(L"Set the blend mode for a layer.");
1307 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1309 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1310 ->text(L"If no argument is given the current blend mode is returned.");
1312 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1313 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1314 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1315 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1316 sink.para()->text(L"Examples:");
1317 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1319 L">> MIXER 1-1 BLEND\n"
1320 L"<< 201 MIXER OK\n"
1321 L"<< SCREEN", L"for getting the current blend mode");
1322 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1325 std::wstring mixer_blend_command(command_context& ctx)
1327 if (ctx.parameters.empty())
1328 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1330 transforms_applier transforms(ctx);
1331 auto value = get_blend_mode(ctx.parameters.at(0));
1332 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1334 transform.image_transform.blend_mode = value;
1339 return L"202 MIXER OK\r\n";
1342 template<typename Getter, typename Setter>
1343 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1345 if (ctx.parameters.empty())
1346 return reply_value(ctx, getter);
1348 transforms_applier transforms(ctx);
1349 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1350 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1351 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1353 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1355 setter(transform, value);
1357 }, duration, tween));
1360 return L"202 MIXER OK\r\n";
1363 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1365 sink.short_description(L"Change the opacity of a layer.");
1366 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1367 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1368 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1369 sink.para()->text(L"Examples:");
1370 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1372 L">> MIXER 1-0 OPACITY\n"
1373 L"<< 201 MIXER OK\n"
1374 L"<< 0.5", L"to retrieve the current opacity");
1377 std::wstring mixer_opacity_command(command_context& ctx)
1379 return single_double_animatable_mixer_command(
1381 [](const frame_transform& t) { return t.image_transform.opacity; },
1382 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1385 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1387 sink.short_description(L"Change the brightness of a layer.");
1388 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1389 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1390 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1391 sink.para()->text(L"Examples:");
1392 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1394 L">> MIXER 1-0 BRIGHTNESS\n"
1395 L"<< 201 MIXER OK\n"
1396 L"0.5", L"to retrieve the current brightness");
1399 std::wstring mixer_brightness_command(command_context& ctx)
1401 return single_double_animatable_mixer_command(
1403 [](const frame_transform& t) { return t.image_transform.brightness; },
1404 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1407 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1409 sink.short_description(L"Change the saturation of a layer.");
1410 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1411 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1412 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1413 sink.para()->text(L"Examples:");
1414 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1416 L">> MIXER 1-0 SATURATION\n"
1417 L"<< 201 MIXER OK\n"
1418 L"<< 0.5", L"to retrieve the current saturation");
1421 std::wstring mixer_saturation_command(command_context& ctx)
1423 return single_double_animatable_mixer_command(
1425 [](const frame_transform& t) { return t.image_transform.saturation; },
1426 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1429 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1431 sink.short_description(L"Change the contrast of a layer.");
1432 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1433 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1434 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1435 sink.para()->text(L"Examples:");
1436 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1438 L">> MIXER 1-0 CONTRAST\n"
1439 L"<< 201 MIXER OK\n"
1440 L"<< 0.5", L"to retrieve the current contrast");
1443 std::wstring mixer_contrast_command(command_context& ctx)
1445 return single_double_animatable_mixer_command(
1447 [](const frame_transform& t) { return t.image_transform.contrast; },
1448 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1451 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1453 sink.short_description(L"Adjust the video levels of a layer.");
1454 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);
1456 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1458 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1459 ->item(L"gamma", L"Adjusts the gamma of the image.")
1460 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1461 sink.para()->text(L"Examples:");
1462 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");
1463 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");
1464 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1466 L">> MIXER 1-10 LEVELS\n"
1467 L"<< 201 MIXER OK\n"
1468 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1471 std::wstring mixer_levels_command(command_context& ctx)
1473 if (ctx.parameters.empty())
1475 auto levels = get_current_transform(ctx).image_transform.levels;
1476 return L"201 MIXER OK\r\n"
1477 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1478 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1479 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1480 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1481 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1484 transforms_applier transforms(ctx);
1486 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1487 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1488 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1489 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1490 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1491 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1492 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1494 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1496 transform.image_transform.levels = value;
1498 }, duration, tween));
1501 return L"202 MIXER OK\r\n";
1504 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1506 sink.short_description(L"Change the fill position and scale of a layer.");
1507 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1509 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1510 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1511 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1512 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1514 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1515 ->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.");
1516 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1518 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1519 ->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, ")
1520 ->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");
1522 ->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.")
1523 ->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.")
1524 ->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.")
1525 ->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.");
1526 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1527 sink.para()->text(L"Examples:");
1528 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1530 L">> MIXER 1-0 FILL\n"
1531 L"<< 201 MIXER OK\n"
1532 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1535 std::wstring mixer_fill_command(command_context& ctx)
1537 if (ctx.parameters.empty())
1539 auto transform = get_current_transform(ctx).image_transform;
1540 auto translation = transform.fill_translation;
1541 auto scale = transform.fill_scale;
1542 return L"201 MIXER OK\r\n"
1543 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1544 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1545 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1546 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1549 transforms_applier transforms(ctx);
1550 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1551 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1552 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1553 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1554 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1555 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1557 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1559 transform.image_transform.fill_translation[0] = x;
1560 transform.image_transform.fill_translation[1] = y;
1561 transform.image_transform.fill_scale[0] = x_s;
1562 transform.image_transform.fill_scale[1] = y_s;
1564 }, duration, tween));
1567 return L"202 MIXER OK\r\n";
1570 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1572 sink.short_description(L"Change the clipping viewport of a layer.");
1573 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1575 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1576 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1577 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1579 ->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.")
1580 ->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.")
1581 ->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.")
1582 ->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.");
1583 sink.para()->text(L"Examples:");
1584 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1586 L">> MIXER 1-0 CLIP\n"
1587 L"<< 201 MIXER OK\n"
1588 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1591 std::wstring mixer_clip_command(command_context& ctx)
1593 if (ctx.parameters.empty())
1595 auto transform = get_current_transform(ctx).image_transform;
1596 auto translation = transform.clip_translation;
1597 auto scale = transform.clip_scale;
1599 return L"201 MIXER OK\r\n"
1600 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1601 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1602 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1603 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1606 transforms_applier transforms(ctx);
1607 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1608 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1609 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1610 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1611 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1612 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1614 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1616 transform.image_transform.clip_translation[0] = x;
1617 transform.image_transform.clip_translation[1] = y;
1618 transform.image_transform.clip_scale[0] = x_s;
1619 transform.image_transform.clip_scale[1] = y_s;
1621 }, duration, tween));
1624 return L"202 MIXER OK\r\n";
1627 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1629 sink.short_description(L"Change the anchor point of a layer.");
1630 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1631 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1633 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1634 ->text(L" will be done from.");
1636 ->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.")
1637 ->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.");
1638 sink.para()->text(L"Examples:");
1639 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1641 L">> MIXER 1-10 ANCHOR\n"
1642 L"<< 201 MIXER OK\n"
1643 L"<< 0.5 0.6", L"gets the anchor point");
1646 std::wstring mixer_anchor_command(command_context& ctx)
1648 if (ctx.parameters.empty())
1650 auto transform = get_current_transform(ctx).image_transform;
1651 auto anchor = transform.anchor;
1652 return L"201 MIXER OK\r\n"
1653 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1654 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1657 transforms_applier transforms(ctx);
1658 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1659 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1660 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1661 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1663 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1665 transform.image_transform.anchor[0] = x;
1666 transform.image_transform.anchor[1] = y;
1668 }, duration, tween));
1671 return L"202 MIXER OK\r\n";
1674 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1676 sink.short_description(L"Crop a layer.");
1677 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);
1679 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1680 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1681 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1683 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1684 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1685 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1686 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1687 sink.para()->text(L"Examples:");
1688 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");
1690 L">> MIXER 1-0 CROP\n"
1691 L"<< 201 MIXER OK\n"
1692 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1695 std::wstring mixer_crop_command(command_context& ctx)
1697 if (ctx.parameters.empty())
1699 auto crop = get_current_transform(ctx).image_transform.crop;
1700 return L"201 MIXER OK\r\n"
1701 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1702 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1703 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1704 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1707 transforms_applier transforms(ctx);
1708 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1709 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1710 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1711 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1712 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1713 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1715 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1717 transform.image_transform.crop.ul[0] = ul_x;
1718 transform.image_transform.crop.ul[1] = ul_y;
1719 transform.image_transform.crop.lr[0] = lr_x;
1720 transform.image_transform.crop.lr[1] = lr_y;
1722 }, duration, tween));
1725 return L"202 MIXER OK\r\n";
1728 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1730 sink.short_description(L"Rotate a layer.");
1731 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1733 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1734 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1735 sink.para()->text(L"Examples:");
1736 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1738 L">> MIXER 1-0 ROTATION\n"
1739 L"<< 201 MIXER OK\n"
1740 L"<< 45", L"to retrieve the current angle");
1743 std::wstring mixer_rotation_command(command_context& ctx)
1745 static const double PI = 3.141592653589793;
1747 return single_double_animatable_mixer_command(
1749 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1750 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1753 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1755 sink.short_description(L"Adjust the perspective transform of a layer.");
1756 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);
1758 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1760 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1761 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1762 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1763 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1764 sink.para()->text(L"Examples:");
1765 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1767 L">> MIXER 1-10 PERSPECTIVE\n"
1768 L"<< 201 MIXER OK\n"
1769 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1772 std::wstring mixer_perspective_command(command_context& ctx)
1774 if (ctx.parameters.empty())
1776 auto perspective = get_current_transform(ctx).image_transform.perspective;
1779 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1780 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1781 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1782 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1783 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1784 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1785 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1786 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1789 transforms_applier transforms(ctx);
1790 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1791 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1792 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1793 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1794 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1795 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1796 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1797 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1798 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1799 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1801 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1803 transform.image_transform.perspective.ul[0] = ul_x;
1804 transform.image_transform.perspective.ul[1] = ul_y;
1805 transform.image_transform.perspective.ur[0] = ur_x;
1806 transform.image_transform.perspective.ur[1] = ur_y;
1807 transform.image_transform.perspective.lr[0] = lr_x;
1808 transform.image_transform.perspective.lr[1] = lr_y;
1809 transform.image_transform.perspective.ll[0] = ll_x;
1810 transform.image_transform.perspective.ll[1] = ll_y;
1812 }, duration, tween));
1815 return L"202 MIXER OK\r\n";
1818 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1820 sink.short_description(L"Enable or disable mipmapping for a layer.");
1821 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1823 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1824 ->text(L"If no argument is given the current state is returned.");
1825 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1826 sink.para()->text(L"Examples:");
1827 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1829 L">> MIXER 1-10 MIPMAP\n"
1830 L"<< 201 MIXER OK\n"
1831 L"<< 1", L"for getting the current state");
1834 std::wstring mixer_mipmap_command(command_context& ctx)
1836 if (ctx.parameters.empty())
1837 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1839 transforms_applier transforms(ctx);
1840 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1841 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1843 transform.image_transform.use_mipmap = value;
1848 return L"202 MIXER OK\r\n";
1851 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1853 sink.short_description(L"Change the volume of a layer.");
1854 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1855 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1856 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1857 sink.para()->text(L"Examples:");
1858 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1859 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1861 L">> MIXER 1-0 VOLUME\n"
1862 L"<< 201 MIXER OK\n"
1863 L"<< 0.8", L"to retrieve the current volume");
1866 std::wstring mixer_volume_command(command_context& ctx)
1868 return single_double_animatable_mixer_command(
1870 [](const frame_transform& t) { return t.audio_transform.volume; },
1871 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1874 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1876 sink.short_description(L"Change the volume of an entire channel.");
1877 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1878 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1879 sink.para()->text(L"Examples:");
1880 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1881 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1882 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1885 std::wstring mixer_mastervolume_command(command_context& ctx)
1887 if (ctx.parameters.empty())
1889 auto volume = ctx.channel.channel->mixer().get_master_volume();
1890 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1893 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1894 ctx.channel.channel->mixer().set_master_volume(master_volume);
1896 return L"202 MIXER OK\r\n";
1899 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1901 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1902 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1903 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1904 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1905 sink.para()->text(L"Examples:");
1906 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1907 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1909 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1910 L"<< 201 MIXER OK\n"
1914 std::wstring mixer_straight_alpha_command(command_context& ctx)
1916 if (ctx.parameters.empty())
1918 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1919 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1922 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1923 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1925 return L"202 MIXER OK\r\n";
1928 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1930 sink.short_description(L"Create a grid of video layers.");
1931 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1933 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1934 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1935 sink.para()->text(L"Examples:");
1936 sink.example(L">> MIXER 1 GRID 2");
1939 std::wstring mixer_grid_command(command_context& ctx)
1941 transforms_applier transforms(ctx);
1942 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1943 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1944 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1945 double delta = 1.0 / static_cast<double>(n);
1946 for (int x = 0; x < n; ++x)
1948 for (int y = 0; y < n; ++y)
1950 int index = x + y*n + 1;
1951 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1953 transform.image_transform.fill_translation[0] = x*delta;
1954 transform.image_transform.fill_translation[1] = y*delta;
1955 transform.image_transform.fill_scale[0] = delta;
1956 transform.image_transform.fill_scale[1] = delta;
1957 transform.image_transform.clip_translation[0] = x*delta;
1958 transform.image_transform.clip_translation[1] = y*delta;
1959 transform.image_transform.clip_scale[0] = delta;
1960 transform.image_transform.clip_scale[1] = delta;
1962 }, duration, tween));
1967 return L"202 MIXER OK\r\n";
1970 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1972 sink.short_description(L"Commit all deferred mixer transforms.");
1973 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1974 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1975 sink.para()->text(L"Examples:");
1977 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1978 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1979 L">> MIXER 1 COMMIT");
1982 std::wstring mixer_commit_command(command_context& ctx)
1984 transforms_applier transforms(ctx);
1985 transforms.commit_deferred();
1987 return L"202 MIXER OK\r\n";
1990 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1992 sink.short_description(L"Clear all transformations on a channel or layer.");
1993 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1994 sink.para()->text(L"Clears all transformations on a channel or layer.");
1995 sink.para()->text(L"Examples:");
1996 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1997 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
2000 std::wstring mixer_clear_command(command_context& ctx)
2002 int layer = ctx.layer_id;
2005 ctx.channel.channel->stage().clear_transforms();
2007 ctx.channel.channel->stage().clear_transforms(layer);
2009 return L"202 MIXER OK\r\n";
2012 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2014 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2015 sink.syntax(L"CHANNEL_GRID");
2016 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2018 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2019 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2022 std::wstring channel_grid_command(command_context& ctx)
2025 auto self = ctx.channels.back();
2027 core::diagnostics::scoped_call_context save;
2028 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2030 std::vector<std::wstring> params;
2031 params.push_back(L"SCREEN");
2032 params.push_back(L"0");
2033 params.push_back(L"NAME");
2034 params.push_back(L"Channel Grid Window");
2035 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
2037 self.channel->output().add(screen);
2039 for (auto& channel : ctx.channels)
2041 if (channel.channel != self.channel)
2043 core::diagnostics::call_context::for_thread().layer = index;
2044 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2045 self.channel->stage().load(index, producer, false);
2046 self.channel->stage().play(index);
2051 auto num_channels = ctx.channels.size() - 1;
2052 int square_side_length = std::ceil(std::sqrt(num_channels));
2054 ctx.channel_index = self.channel->index();
2056 ctx.parameters.clear();
2057 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2058 mixer_grid_command(ctx);
2060 return L"202 CHANNEL_GRID OK\r\n";
2063 // Thumbnail Commands
2065 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2067 sink.short_description(L"List all thumbnails.");
2068 sink.syntax(L"THUMBNAIL LIST");
2069 sink.para()->text(L"Lists all thumbnails.");
2070 sink.para()->text(L"Examples:");
2072 L">> THUMBNAIL LIST\n"
2073 L"<< 200 THUMBNAIL LIST OK\n"
2074 L"<< \"AMB\" 20130301T124409 1149\n"
2075 L"<< \"foo/bar\" 20130523T234001 244");
2078 std::wstring thumbnail_list_command(command_context& ctx)
2080 std::wstringstream replyString;
2081 replyString << L"200 THUMBNAIL LIST OK\r\n";
2083 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2085 if (boost::filesystem::is_regular_file(itr->path()))
2087 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2090 auto relativePath = get_relative_without_extension(itr->path(), env::thumbnails_folder());
2091 auto str = relativePath.generic_wstring();
2093 if (str[0] == '\\' || str[0] == '/')
2094 str = std::wstring(str.begin() + 1, str.end());
2096 auto mtime = boost::filesystem::last_write_time(itr->path());
2097 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2098 auto file_size = boost::filesystem::file_size(itr->path());
2100 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2104 replyString << L"\r\n";
2106 return boost::to_upper_copy(replyString.str());
2109 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2111 sink.short_description(L"Retrieve a thumbnail.");
2112 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2113 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2114 sink.para()->text(L"Examples:");
2116 L">> THUMBNAIL RETRIEVE foo/bar\n"
2117 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2118 L"<< ...base64 data...");
2121 std::wstring thumbnail_retrieve_command(command_context& ctx)
2123 std::wstring filename = env::thumbnails_folder();
2124 filename.append(ctx.parameters.at(0));
2125 filename.append(L".png");
2127 std::wstring file_contents;
2129 auto found_file = find_case_insensitive(filename);
2132 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2134 if (file_contents.empty())
2135 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2137 std::wstringstream reply;
2139 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2140 reply << file_contents;
2145 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2147 sink.short_description(L"Regenerate a thumbnail.");
2148 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2149 sink.para()->text(L"Regenerates a thumbnail.");
2152 std::wstring thumbnail_generate_command(command_context& ctx)
2156 ctx.thumb_gen->generate(ctx.parameters.at(0));
2157 return L"202 THUMBNAIL GENERATE OK\r\n";
2160 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2163 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2165 sink.short_description(L"Regenerate all thumbnails.");
2166 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2167 sink.para()->text(L"Regenerates all thumbnails.");
2170 std::wstring thumbnail_generateall_command(command_context& ctx)
2174 ctx.thumb_gen->generate_all();
2175 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2178 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2183 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2185 sink.short_description(L"Get information about a media file.");
2186 sink.syntax(L"CINF [filename:string]");
2187 sink.para()->text(L"Returns information about a media file.");
2190 std::wstring cinf_command(command_context& ctx)
2193 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2195 auto path = itr->path();
2196 auto file = path.replace_extension(L"").filename().wstring();
2197 if (boost::iequals(file, ctx.parameters.at(0)))
2198 info += MediaInfo(itr->path(), ctx.media_info_repo);
2202 CASPAR_THROW_EXCEPTION(file_not_found());
2204 std::wstringstream replyString;
2205 replyString << L"200 CINF OK\r\n";
2206 replyString << info << "\r\n";
2208 return replyString.str();
2211 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2213 sink.short_description(L"List all media files.");
2214 sink.syntax(L"CLS");
2216 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2217 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2220 std::wstring cls_command(command_context& ctx)
2222 std::wstringstream replyString;
2223 replyString << L"200 CLS OK\r\n";
2224 replyString << ListMedia(ctx.media_info_repo);
2225 replyString << L"\r\n";
2226 return boost::to_upper_copy(replyString.str());
2229 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2231 sink.short_description(L"List all fonts.");
2232 sink.syntax(L"FLS");
2234 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2235 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2236 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2239 std::wstring fls_command(command_context& ctx)
2241 std::wstringstream replyString;
2242 replyString << L"200 FLS OK\r\n";
2244 for (auto& font : core::text::list_fonts())
2245 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2247 replyString << L"\r\n";
2249 return replyString.str();
2252 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2254 sink.short_description(L"List all templates.");
2255 sink.syntax(L"TLS");
2257 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2258 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2261 std::wstring tls_command(command_context& ctx)
2263 std::wstringstream replyString;
2264 replyString << L"200 TLS OK\r\n";
2266 replyString << ListTemplates(ctx.cg_registry);
2267 replyString << L"\r\n";
2269 return replyString.str();
2272 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2274 sink.short_description(L"Get version information.");
2275 sink.syntax(L"VERSION {[component:string]}");
2276 sink.para()->text(L"Returns the version of specified component.");
2277 sink.para()->text(L"Examples:");
2280 L"<< 201 VERSION OK\n"
2281 L"<< 2.1.0.f207a33 STABLE");
2283 L">> VERSION SERVER\n"
2284 L"<< 201 VERSION OK\n"
2285 L"<< 2.1.0.f207a33 STABLE");
2287 L">> VERSION FLASH\n"
2288 L"<< 201 VERSION OK\n"
2292 std::wstring version_command(command_context& ctx)
2294 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2296 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2298 return L"201 VERSION OK\r\n" + version + L"\r\n";
2301 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2304 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2306 sink.short_description(L"Get a list of the available channels.");
2307 sink.syntax(L"INFO");
2308 sink.para()->text(L"Retrieves a list of the available channels.");
2312 L"<< 1 720p5000 PLAYING\n"
2313 L"<< 2 PAL PLAYING");
2316 std::wstring info_command(command_context& ctx)
2318 std::wstringstream replyString;
2319 // This is needed for backwards compatibility with old clients
2320 replyString << L"200 INFO OK\r\n";
2321 for (size_t n = 0; n < ctx.channels.size(); ++n)
2322 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2323 replyString << L"\r\n";
2324 return replyString.str();
2327 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2329 std::wstringstream replyString;
2331 if (command.empty())
2332 replyString << L"201 INFO OK\r\n";
2334 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2336 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2337 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2338 replyString << L"\r\n";
2339 return replyString.str();
2342 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2344 sink.short_description(L"Get information about a channel or a layer.");
2345 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2346 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2347 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2350 std::wstring info_channel_command(command_context& ctx)
2352 boost::property_tree::wptree info;
2353 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2355 if (layer == std::numeric_limits<int>::min())
2357 info.add_child(L"channel", ctx.channel.channel->info())
2358 .add(L"index", ctx.channel_index);
2362 if (ctx.parameters.size() >= 1)
2364 if (boost::iequals(ctx.parameters.at(0), L"B"))
2365 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2367 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2371 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2375 return create_info_xml_reply(info);
2378 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2380 sink.short_description(L"Get information about a template.");
2381 sink.syntax(L"INFO TEMPLATE [template:string]");
2382 sink.para()->text(L"Gets information about the specified template.");
2385 std::wstring info_template_command(command_context& ctx)
2387 auto filename = ctx.parameters.at(0);
2389 std::wstringstream str;
2390 str << u16(ctx.cg_registry->read_meta_info(filename));
2391 boost::property_tree::wptree info;
2392 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2394 return create_info_xml_reply(info, L"TEMPLATE");
2397 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2399 sink.short_description(L"Get the contents of the configuration used.");
2400 sink.syntax(L"INFO CONFIG");
2401 sink.para()->text(L"Gets the contents of the configuration used.");
2404 std::wstring info_config_command(command_context& ctx)
2406 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2409 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2411 sink.short_description(L"Get information about the paths used.");
2412 sink.syntax(L"INFO PATHS");
2413 sink.para()->text(L"Gets information about the paths used.");
2416 std::wstring info_paths_command(command_context& ctx)
2418 boost::property_tree::wptree info;
2419 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2420 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2422 return create_info_xml_reply(info, L"PATHS");
2425 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2427 sink.short_description(L"Get system information.");
2428 sink.syntax(L"INFO SYSTEM");
2429 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2432 std::wstring info_system_command(command_context& ctx)
2434 boost::property_tree::wptree info;
2436 info.add(L"system.name", caspar::system_product_name());
2437 info.add(L"system.os.description", caspar::os_description());
2438 info.add(L"system.cpu", caspar::cpu_info());
2440 ctx.system_info_repo->fill_information(info);
2442 return create_info_xml_reply(info, L"SYSTEM");
2445 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2447 sink.short_description(L"Get detailed information about all channels.");
2448 sink.syntax(L"INFO SERVER");
2449 sink.para()->text(L"Gets detailed information about all channels.");
2452 std::wstring info_server_command(command_context& ctx)
2454 boost::property_tree::wptree info;
2457 for (auto& channel : ctx.channels)
2458 info.add_child(L"channels.channel", channel.channel->info())
2459 .add(L"index", ++index);
2461 return create_info_xml_reply(info, L"SERVER");
2464 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2466 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2467 sink.syntax(L"INFO QUEUES");
2468 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2471 std::wstring info_queues_command(command_context& ctx)
2473 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2476 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2478 sink.short_description(L"Lists all known threads in the server.");
2479 sink.syntax(L"INFO THREADS");
2480 sink.para()->text(L"Lists all known threads in the server.");
2483 std::wstring info_threads_command(command_context& ctx)
2485 std::wstringstream replyString;
2486 replyString << L"200 INFO THREADS OK\r\n";
2488 for (auto& thread : get_thread_infos())
2490 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2493 replyString << L"\r\n";
2494 return replyString.str();
2497 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2499 sink.short_description(L"Get the current delay on a channel or a layer.");
2500 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2501 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2504 std::wstring info_delay_command(command_context& ctx)
2506 boost::property_tree::wptree info;
2507 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2509 if (layer == std::numeric_limits<int>::min())
2510 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2512 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2513 .add(L"index", layer);
2515 return create_info_xml_reply(info, L"DELAY");
2518 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2520 sink.short_description(L"Open the diagnostics window.");
2521 sink.syntax(L"DIAG");
2522 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2525 std::wstring diag_command(command_context& ctx)
2527 core::diagnostics::osd::show_graphs(true);
2529 return L"202 DIAG OK\r\n";
2532 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2534 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2535 sink.syntax(L"GL INFO");
2536 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2539 std::wstring gl_info_command(command_context& ctx)
2541 auto device = ctx.ogl_device;
2544 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2546 std::wstringstream result;
2547 result << L"201 GL INFO OK\r\n";
2549 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2550 auto info = device->info();
2551 boost::property_tree::write_xml(result, info, w);
2554 return result.str();
2557 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2559 sink.short_description(L"Release pooled OpenGL resources.");
2560 sink.syntax(L"GL GC");
2561 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2564 std::wstring gl_gc_command(command_context& ctx)
2566 auto device = ctx.ogl_device;
2569 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2571 device->gc().wait();
2573 return L"202 GL GC OK\r\n";
2576 static const int WIDTH = 80;
2578 struct max_width_sink : public core::help_sink
2580 std::size_t max_width = 0;
2582 void begin_item(const std::wstring& name) override
2584 max_width = std::max(name.length(), max_width);
2588 struct short_description_sink : public core::help_sink
2591 std::wstringstream& out;
2593 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2595 void begin_item(const std::wstring& name) override
2597 out << std::left << std::setw(width + 1) << name;
2600 void short_description(const std::wstring& short_description) override
2602 out << short_description << L"\r\n";
2606 struct simple_paragraph_builder : core::paragraph_builder
2608 std::wostringstream out;
2609 std::wstringstream& commit_to;
2611 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2612 ~simple_paragraph_builder()
2614 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2616 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2618 out << std::move(text);
2619 return shared_from_this();
2621 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2622 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2623 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2624 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2627 struct simple_definition_list_builder : core::definition_list_builder
2629 std::wstringstream& out;
2631 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2632 ~simple_definition_list_builder()
2637 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2639 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2640 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2641 return shared_from_this();
2645 struct long_description_sink : public core::help_sink
2647 std::wstringstream& out;
2649 long_description_sink(std::wstringstream& out) : out(out) { }
2651 void syntax(const std::wstring& syntax) override
2653 out << L"Syntax:\n";
2654 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2657 spl::shared_ptr<core::paragraph_builder> para() override
2659 return spl::make_shared<simple_paragraph_builder>(out);
2662 spl::shared_ptr<core::definition_list_builder> definitions() override
2664 return spl::make_shared<simple_definition_list_builder>(out);
2667 void example(const std::wstring& code, const std::wstring& caption) override
2669 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2671 if (!caption.empty())
2672 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2677 void begin_item(const std::wstring& name) override
2679 out << name << L"\n\n";
2683 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2685 std::wstringstream result;
2686 result << L"200 " << help_command << L" OK\r\n";
2687 max_width_sink width;
2688 ctx.help_repo->help(tags, width);
2689 short_description_sink sink(width.max_width, result);
2690 sink.width = width.max_width;
2691 ctx.help_repo->help(tags, sink);
2693 return result.str();
2696 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2698 std::wstringstream result;
2699 result << L"201 " << help_command << L" OK\r\n";
2700 auto joined = boost::join(ctx.parameters, L" ");
2701 long_description_sink sink(result);
2702 ctx.help_repo->help(tags, joined, sink);
2704 return result.str();
2707 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2709 sink.short_description(L"Show online help for AMCP commands.");
2710 sink.syntax(LR"(HELP {[command:string]})");
2711 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2712 sink.example(L">> HELP", L"Shows a list of commands.");
2713 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2716 std::wstring help_command(command_context& ctx)
2718 if (ctx.parameters.size() == 0)
2719 return create_help_list(L"HELP", ctx, { L"AMCP" });
2721 return create_help_details(L"HELP", ctx, { L"AMCP" });
2724 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2726 sink.short_description(L"Show online help for producers.");
2727 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2728 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2729 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2730 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2733 std::wstring help_producer_command(command_context& ctx)
2735 if (ctx.parameters.size() == 0)
2736 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2738 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2741 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2743 sink.short_description(L"Show online help for consumers.");
2744 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2745 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2746 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2747 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2750 std::wstring help_consumer_command(command_context& ctx)
2752 if (ctx.parameters.size() == 0)
2753 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2755 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2758 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2760 sink.short_description(L"Disconnect the session.");
2761 sink.syntax(L"BYE");
2763 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2764 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2767 std::wstring bye_command(command_context& ctx)
2769 ctx.client->disconnect();
2773 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2775 sink.short_description(L"Shutdown the server.");
2776 sink.syntax(L"KILL");
2777 sink.para()->text(L"Shuts the server down.");
2780 std::wstring kill_command(command_context& ctx)
2782 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2783 return L"202 KILL OK\r\n";
2786 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2788 sink.short_description(L"Shutdown the server with restart exit code.");
2789 sink.syntax(L"RESTART");
2791 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2792 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2795 std::wstring restart_command(command_context& ctx)
2797 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2798 return L"202 RESTART OK\r\n";
2801 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2803 sink.short_description(L"Lock or unlock access to a channel.");
2804 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2805 sink.para()->text(L"Allows for exclusive access to a channel.");
2806 sink.para()->text(L"Examples:");
2807 sink.example(L"LOCK 1 ACQUIRE secret");
2808 sink.example(L"LOCK 1 RELEASE");
2809 sink.example(L"LOCK 1 CLEAR");
2812 std::wstring lock_command(command_context& ctx)
2814 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2815 auto lock = ctx.channels.at(channel_index).lock;
2816 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2818 if (command == L"ACQUIRE")
2820 std::wstring lock_phrase = ctx.parameters.at(2);
2822 //TODO: read options
2824 //just lock one channel
2825 if (!lock->try_lock(lock_phrase, ctx.client))
2826 return L"503 LOCK ACQUIRE FAILED\r\n";
2828 return L"202 LOCK ACQUIRE OK\r\n";
2830 else if (command == L"RELEASE")
2832 lock->release_lock(ctx.client);
2833 return L"202 LOCK RELEASE OK\r\n";
2835 else if (command == L"CLEAR")
2837 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2838 std::wstring client_override_phrase;
2840 if (!override_phrase.empty())
2841 client_override_phrase = ctx.parameters.at(2);
2843 //just clear one channel
2844 if (client_override_phrase != override_phrase)
2845 return L"503 LOCK CLEAR FAILED\r\n";
2847 lock->clear_locks();
2849 return L"202 LOCK CLEAR OK\r\n";
2852 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2855 void register_commands(amcp_command_repository& repo)
2857 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2858 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2859 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2860 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2861 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2862 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2863 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2864 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2865 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2866 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2867 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2868 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2869 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2870 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2871 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2873 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2874 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2875 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2876 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2878 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2879 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2880 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2881 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2882 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2883 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2884 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2885 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2886 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2888 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2889 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2890 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2891 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2892 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2893 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2894 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2895 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2896 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2897 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2898 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2899 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2900 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2901 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2902 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2903 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2904 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2905 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2906 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2907 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2908 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2909 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2911 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2912 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2913 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2914 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2916 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2917 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2918 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
2919 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2920 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2921 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2922 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2923 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2924 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2925 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2926 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2927 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2928 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2929 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2930 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2931 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2932 repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
2933 repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
2934 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2935 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2936 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2937 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2938 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2939 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2943 }} //namespace caspar