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(user_error() << 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(user_error() << msg_info(L"Invalid audio channel layout"));
749 CASPAR_THROW_EXCEPTION(user_error() << 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 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
904 sink.short_description(L"Prepare a template for displaying.");
905 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
907 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
908 ->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.");
909 sink.para()->text(L"Examples:");
910 sink.example(L"CG 1 ADD 10 svtnews/info 1");
913 std::wstring cg_add_command(command_context& ctx)
915 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
917 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
918 std::wstring label; //_parameters[2]
919 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
920 unsigned int dataIndex = 3;
922 if (ctx.parameters.at(2).length() > 1)
924 label = ctx.parameters.at(2);
927 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
928 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
931 { //read play-on-load-flag
932 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
935 const wchar_t* pDataString = 0;
936 std::wstring dataFromFile;
937 if (ctx.parameters.size() > dataIndex)
939 const std::wstring& dataString = ctx.parameters.at(dataIndex);
941 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
942 pDataString = dataString.c_str();
945 //The data is not an XML-string, it must be a filename
946 std::wstring filename = env::data_folder();
947 filename.append(dataString);
948 filename.append(L".ftd");
950 auto found_file = find_case_insensitive(filename);
954 dataFromFile = read_file(boost::filesystem::path(*found_file));
955 pDataString = dataFromFile.c_str();
960 auto filename = ctx.parameters.at(1);
961 auto proxy = ctx.cg_registry->get_or_create_proxy(
962 spl::make_shared_ptr(ctx.channel.channel),
963 get_producer_dependencies(ctx.channel.channel, ctx),
964 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
967 if (proxy == core::cg_proxy::empty())
968 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
970 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
972 return L"202 CG OK\r\n";
975 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
977 sink.short_description(L"Play and display a template.");
978 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
979 sink.para()->text(L"Plays and displays the template in the specified layer.");
980 sink.para()->text(L"Examples:");
981 sink.example(L"CG 1 PLAY 0");
984 std::wstring cg_play_command(command_context& ctx)
986 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
987 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
989 return L"202 CG OK\r\n";
992 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
994 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
996 if (proxy == cg_proxy::empty())
997 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"No CG proxy running on layer"));
1002 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1004 sink.short_description(L"Stop and remove a template.");
1005 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1007 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1008 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1009 sink.para()->text(L"Examples:");
1010 sink.example(L"CG 1 STOP 0");
1013 std::wstring cg_stop_command(command_context& ctx)
1015 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1016 get_expected_cg_proxy(ctx)->stop(layer, 0);
1018 return L"202 CG OK\r\n";
1021 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1023 sink.short_description(LR"(Trigger a "continue" in a template.)");
1024 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1026 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1027 ->text(L"This is used to control animations that has multiple discreet steps.");
1028 sink.para()->text(L"Examples:");
1029 sink.example(L"CG 1 NEXT 0");
1032 std::wstring cg_next_command(command_context& ctx)
1034 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1035 get_expected_cg_proxy(ctx)->next(layer);
1037 return L"202 CG OK\r\n";
1040 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1042 sink.short_description(L"Remove a template.");
1043 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1044 sink.para()->text(L"Removes the template from the specified layer.");
1045 sink.para()->text(L"Examples:");
1046 sink.example(L"CG 1 REMOVE 0");
1049 std::wstring cg_remove_command(command_context& ctx)
1051 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1052 get_expected_cg_proxy(ctx)->remove(layer);
1054 return L"202 CG OK\r\n";
1057 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1059 sink.short_description(L"Remove all templates on a video layer.");
1060 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1061 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1062 sink.para()->text(L"Examples:");
1063 sink.example(L"CG 1 CLEAR");
1066 std::wstring cg_clear_command(command_context& ctx)
1068 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1070 return L"202 CG OK\r\n";
1073 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1075 sink.short_description(L"Update a template with new data.");
1076 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1077 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.");
1080 std::wstring cg_update_command(command_context& ctx)
1082 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1084 std::wstring dataString = ctx.parameters.at(1);
1085 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1087 //The data is not XML or Json, it must be a filename
1088 std::wstring filename = env::data_folder();
1089 filename.append(dataString);
1090 filename.append(L".ftd");
1092 dataString = read_file(boost::filesystem::path(filename));
1095 get_expected_cg_proxy(ctx)->update(layer, dataString);
1097 return L"202 CG OK\r\n";
1100 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1102 sink.short_description(L"Invoke a method/label on a template.");
1103 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1104 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1105 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1108 std::wstring cg_invoke_command(command_context& ctx)
1110 std::wstringstream replyString;
1111 replyString << L"201 CG OK\r\n";
1112 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1113 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1114 replyString << result << L"\r\n";
1116 return replyString.str();
1119 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1121 sink.short_description(L"Get information about a running template or the template host.");
1122 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1123 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1124 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1127 std::wstring cg_info_command(command_context& ctx)
1129 std::wstringstream replyString;
1130 replyString << L"201 CG OK\r\n";
1132 if (ctx.parameters.empty())
1134 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1135 replyString << info << L"\r\n";
1139 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1140 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1142 replyString << desc << L"\r\n";
1145 return replyString.str();
1150 core::frame_transform get_current_transform(command_context& ctx)
1152 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1155 template<typename Func>
1156 std::wstring reply_value(command_context& ctx, const Func& extractor)
1158 auto value = extractor(get_current_transform(ctx));
1160 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1163 class transforms_applier
1165 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1167 std::vector<stage::transform_tuple_t> transforms_;
1168 command_context& ctx_;
1171 transforms_applier(command_context& ctx)
1174 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1177 ctx.parameters.pop_back();
1180 void add(stage::transform_tuple_t&& transform)
1182 transforms_.push_back(std::move(transform));
1185 void commit_deferred()
1187 auto& transforms = deferred_transforms_[ctx_.channel_index];
1188 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1196 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1197 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1200 ctx_.channel.channel->stage().apply_transforms(transforms_);
1203 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1205 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1207 sink.short_description(L"Let a layer act as alpha for the one obove.");
1208 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1210 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1211 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1212 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1213 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1214 ->text(L"instead it will be used as the key for the layer above.");
1215 sink.para()->text(L"Examples:");
1216 sink.example(L">> MIXER 1-0 KEYER 1");
1218 L">> MIXER 1-0 KEYER\n"
1219 L"<< 201 MIXER OK\n"
1220 L"<< 1", L"to retrieve the current state");
1223 std::wstring mixer_keyer_command(command_context& ctx)
1225 if (ctx.parameters.empty())
1226 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1228 transforms_applier transforms(ctx);
1229 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1230 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1232 transform.image_transform.is_key = value;
1237 return L"202 MIXER OK\r\n";
1240 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1242 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1244 sink.short_description(L"Enable chroma keying on a layer.");
1245 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1247 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1248 sink.para()->text(L"Examples:");
1249 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1250 sink.example(L">> MIXER 1-1 CHROMA none");
1252 L">> MIXER 1-1 BLEND\n"
1253 L"<< 201 MIXER OK\n"
1254 L"<< SCREEN", L"for getting the current blend mode");
1257 std::wstring mixer_chroma_command(command_context& ctx)
1259 if (ctx.parameters.empty())
1261 auto chroma = get_current_transform(ctx).image_transform.chroma;
1262 return L"201 MIXER OK\r\n"
1263 + core::get_chroma_mode(chroma.key) + L" "
1264 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1265 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1266 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1269 transforms_applier transforms(ctx);
1270 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1271 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1273 core::chroma chroma;
1274 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1276 if (chroma.key != core::chroma::type::none)
1278 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1279 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1280 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1283 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1285 transform.image_transform.chroma = chroma;
1287 }, duration, tween));
1290 return L"202 MIXER OK\r\n";
1293 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1295 sink.short_description(L"Set the blend mode for a layer.");
1296 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1298 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1299 ->text(L"If no argument is given the current blend mode is returned.");
1301 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1302 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1303 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1304 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1305 sink.para()->text(L"Examples:");
1306 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1308 L">> MIXER 1-1 BLEND\n"
1309 L"<< 201 MIXER OK\n"
1310 L"<< SCREEN", L"for getting the current blend mode");
1311 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1314 std::wstring mixer_blend_command(command_context& ctx)
1316 if (ctx.parameters.empty())
1317 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1319 transforms_applier transforms(ctx);
1320 auto value = get_blend_mode(ctx.parameters.at(0));
1321 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1323 transform.image_transform.blend_mode = value;
1328 return L"202 MIXER OK\r\n";
1331 template<typename Getter, typename Setter>
1332 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1334 if (ctx.parameters.empty())
1335 return reply_value(ctx, getter);
1337 transforms_applier transforms(ctx);
1338 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1339 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1340 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1342 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1344 setter(transform, value);
1346 }, duration, tween));
1349 return L"202 MIXER OK\r\n";
1352 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1354 sink.short_description(L"Change the opacity of a layer.");
1355 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1356 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1357 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1358 sink.para()->text(L"Examples:");
1359 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1361 L">> MIXER 1-0 OPACITY\n"
1362 L"<< 201 MIXER OK\n"
1363 L"<< 0.5", L"to retrieve the current opacity");
1366 std::wstring mixer_opacity_command(command_context& ctx)
1368 return single_double_animatable_mixer_command(
1370 [](const frame_transform& t) { return t.image_transform.opacity; },
1371 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1374 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1376 sink.short_description(L"Change the brightness of a layer.");
1377 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1378 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1379 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1380 sink.para()->text(L"Examples:");
1381 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1383 L">> MIXER 1-0 BRIGHTNESS\n"
1384 L"<< 201 MIXER OK\n"
1385 L"0.5", L"to retrieve the current brightness");
1388 std::wstring mixer_brightness_command(command_context& ctx)
1390 return single_double_animatable_mixer_command(
1392 [](const frame_transform& t) { return t.image_transform.brightness; },
1393 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1396 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1398 sink.short_description(L"Change the saturation of a layer.");
1399 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1400 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1401 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1402 sink.para()->text(L"Examples:");
1403 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1405 L">> MIXER 1-0 SATURATION\n"
1406 L"<< 201 MIXER OK\n"
1407 L"<< 0.5", L"to retrieve the current saturation");
1410 std::wstring mixer_saturation_command(command_context& ctx)
1412 return single_double_animatable_mixer_command(
1414 [](const frame_transform& t) { return t.image_transform.saturation; },
1415 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1418 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1420 sink.short_description(L"Change the contrast of a layer.");
1421 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1422 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1423 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1424 sink.para()->text(L"Examples:");
1425 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1427 L">> MIXER 1-0 CONTRAST\n"
1428 L"<< 201 MIXER OK\n"
1429 L"<< 0.5", L"to retrieve the current contrast");
1432 std::wstring mixer_contrast_command(command_context& ctx)
1434 return single_double_animatable_mixer_command(
1436 [](const frame_transform& t) { return t.image_transform.contrast; },
1437 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1440 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1442 sink.short_description(L"Adjust the video levels of a layer.");
1443 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);
1445 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1447 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1448 ->item(L"gamma", L"Adjusts the gamma of the image.")
1449 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1450 sink.para()->text(L"Examples:");
1451 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");
1452 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");
1453 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1455 L">> MIXER 1-10 LEVELS\n"
1456 L"<< 201 MIXER OK\n"
1457 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1460 std::wstring mixer_levels_command(command_context& ctx)
1462 if (ctx.parameters.empty())
1464 auto levels = get_current_transform(ctx).image_transform.levels;
1465 return L"201 MIXER OK\r\n"
1466 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1467 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1468 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1469 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1470 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1473 transforms_applier transforms(ctx);
1475 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1476 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1477 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1478 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1479 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1480 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1481 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1483 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1485 transform.image_transform.levels = value;
1487 }, duration, tween));
1490 return L"202 MIXER OK\r\n";
1493 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1495 sink.short_description(L"Change the fill position and scale of a layer.");
1496 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1498 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1499 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1500 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1501 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1503 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1504 ->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.");
1505 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1507 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1508 ->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, ")
1509 ->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");
1511 ->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.")
1512 ->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.")
1513 ->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.")
1514 ->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.");
1515 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1516 sink.para()->text(L"Examples:");
1517 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1519 L">> MIXER 1-0 FILL\n"
1520 L"<< 201 MIXER OK\n"
1521 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1524 std::wstring mixer_fill_command(command_context& ctx)
1526 if (ctx.parameters.empty())
1528 auto transform = get_current_transform(ctx).image_transform;
1529 auto translation = transform.fill_translation;
1530 auto scale = transform.fill_scale;
1531 return L"201 MIXER OK\r\n"
1532 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1533 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1534 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1535 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1538 transforms_applier transforms(ctx);
1539 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1540 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1541 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1542 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1543 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1544 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1546 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1548 transform.image_transform.fill_translation[0] = x;
1549 transform.image_transform.fill_translation[1] = y;
1550 transform.image_transform.fill_scale[0] = x_s;
1551 transform.image_transform.fill_scale[1] = y_s;
1553 }, duration, tween));
1556 return L"202 MIXER OK\r\n";
1559 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1561 sink.short_description(L"Change the clipping viewport of a layer.");
1562 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1564 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1565 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1566 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1568 ->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.")
1569 ->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.")
1570 ->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.")
1571 ->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.");
1572 sink.para()->text(L"Examples:");
1573 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1575 L">> MIXER 1-0 CLIP\n"
1576 L"<< 201 MIXER OK\n"
1577 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1580 std::wstring mixer_clip_command(command_context& ctx)
1582 if (ctx.parameters.empty())
1584 auto transform = get_current_transform(ctx).image_transform;
1585 auto translation = transform.clip_translation;
1586 auto scale = transform.clip_scale;
1588 return L"201 MIXER OK\r\n"
1589 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1590 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1591 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1592 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1595 transforms_applier transforms(ctx);
1596 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1597 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1598 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1599 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1600 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1601 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1603 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1605 transform.image_transform.clip_translation[0] = x;
1606 transform.image_transform.clip_translation[1] = y;
1607 transform.image_transform.clip_scale[0] = x_s;
1608 transform.image_transform.clip_scale[1] = y_s;
1610 }, duration, tween));
1613 return L"202 MIXER OK\r\n";
1616 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1618 sink.short_description(L"Change the anchor point of a layer.");
1619 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1620 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1622 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1623 ->text(L" will be done from.");
1625 ->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.")
1626 ->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.");
1627 sink.para()->text(L"Examples:");
1628 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1630 L">> MIXER 1-10 ANCHOR\n"
1631 L"<< 201 MIXER OK\n"
1632 L"<< 0.5 0.6", L"gets the anchor point");
1635 std::wstring mixer_anchor_command(command_context& ctx)
1637 if (ctx.parameters.empty())
1639 auto transform = get_current_transform(ctx).image_transform;
1640 auto anchor = transform.anchor;
1641 return L"201 MIXER OK\r\n"
1642 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1643 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1646 transforms_applier transforms(ctx);
1647 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1648 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1649 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1650 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1652 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1654 transform.image_transform.anchor[0] = x;
1655 transform.image_transform.anchor[1] = y;
1657 }, duration, tween));
1660 return L"202 MIXER OK\r\n";
1663 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1665 sink.short_description(L"Crop a layer.");
1666 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);
1668 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1669 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1670 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1672 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1673 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1674 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1675 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1676 sink.para()->text(L"Examples:");
1677 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");
1679 L">> MIXER 1-0 CROP\n"
1680 L"<< 201 MIXER OK\n"
1681 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1684 std::wstring mixer_crop_command(command_context& ctx)
1686 if (ctx.parameters.empty())
1688 auto crop = get_current_transform(ctx).image_transform.crop;
1689 return L"201 MIXER OK\r\n"
1690 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1691 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1692 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1693 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1696 transforms_applier transforms(ctx);
1697 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1698 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1699 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1700 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1701 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1702 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1704 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1706 transform.image_transform.crop.ul[0] = ul_x;
1707 transform.image_transform.crop.ul[1] = ul_y;
1708 transform.image_transform.crop.lr[0] = lr_x;
1709 transform.image_transform.crop.lr[1] = lr_y;
1711 }, duration, tween));
1714 return L"202 MIXER OK\r\n";
1717 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1719 sink.short_description(L"Rotate a layer.");
1720 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1722 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1723 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1724 sink.para()->text(L"Examples:");
1725 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1727 L">> MIXER 1-0 ROTATION\n"
1728 L"<< 201 MIXER OK\n"
1729 L"<< 45", L"to retrieve the current angle");
1732 std::wstring mixer_rotation_command(command_context& ctx)
1734 static const double PI = 3.141592653589793;
1736 return single_double_animatable_mixer_command(
1738 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1739 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1742 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1744 sink.short_description(L"Adjust the perspective transform of a layer.");
1745 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);
1747 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1749 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1750 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1751 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1752 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1753 sink.para()->text(L"Examples:");
1754 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1756 L">> MIXER 1-10 PERSPECTIVE\n"
1757 L"<< 201 MIXER OK\n"
1758 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1761 std::wstring mixer_perspective_command(command_context& ctx)
1763 if (ctx.parameters.empty())
1765 auto perspective = get_current_transform(ctx).image_transform.perspective;
1768 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1769 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1770 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1771 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1772 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1773 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1774 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1775 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1778 transforms_applier transforms(ctx);
1779 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1780 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1781 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1782 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1783 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1784 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1785 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1786 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1787 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1788 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1790 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1792 transform.image_transform.perspective.ul[0] = ul_x;
1793 transform.image_transform.perspective.ul[1] = ul_y;
1794 transform.image_transform.perspective.ur[0] = ur_x;
1795 transform.image_transform.perspective.ur[1] = ur_y;
1796 transform.image_transform.perspective.lr[0] = lr_x;
1797 transform.image_transform.perspective.lr[1] = lr_y;
1798 transform.image_transform.perspective.ll[0] = ll_x;
1799 transform.image_transform.perspective.ll[1] = ll_y;
1801 }, duration, tween));
1804 return L"202 MIXER OK\r\n";
1807 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1809 sink.short_description(L"Enable or disable mipmapping for a layer.");
1810 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1812 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1813 ->text(L"If no argument is given the current state is returned.");
1814 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1815 sink.para()->text(L"Examples:");
1816 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1818 L">> MIXER 1-10 MIPMAP\n"
1819 L"<< 201 MIXER OK\n"
1820 L"<< 1", L"for getting the current state");
1823 std::wstring mixer_mipmap_command(command_context& ctx)
1825 if (ctx.parameters.empty())
1826 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1828 transforms_applier transforms(ctx);
1829 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1830 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1832 transform.image_transform.use_mipmap = value;
1837 return L"202 MIXER OK\r\n";
1840 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1842 sink.short_description(L"Change the volume of a layer.");
1843 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1844 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1845 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1846 sink.para()->text(L"Examples:");
1847 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1848 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1850 L">> MIXER 1-0 VOLUME\n"
1851 L"<< 201 MIXER OK\n"
1852 L"<< 0.8", L"to retrieve the current volume");
1855 std::wstring mixer_volume_command(command_context& ctx)
1857 return single_double_animatable_mixer_command(
1859 [](const frame_transform& t) { return t.audio_transform.volume; },
1860 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1863 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1865 sink.short_description(L"Change the volume of an entire channel.");
1866 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1867 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1868 sink.para()->text(L"Examples:");
1869 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1870 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1871 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1874 std::wstring mixer_mastervolume_command(command_context& ctx)
1876 if (ctx.parameters.empty())
1878 auto volume = ctx.channel.channel->mixer().get_master_volume();
1879 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1882 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1883 ctx.channel.channel->mixer().set_master_volume(master_volume);
1885 return L"202 MIXER OK\r\n";
1888 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1890 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1891 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1892 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1893 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1894 sink.para()->text(L"Examples:");
1895 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1896 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1898 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1899 L"<< 201 MIXER OK\n"
1903 std::wstring mixer_straight_alpha_command(command_context& ctx)
1905 if (ctx.parameters.empty())
1907 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1908 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1911 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1912 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1914 return L"202 MIXER OK\r\n";
1917 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1919 sink.short_description(L"Create a grid of video layers.");
1920 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1922 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1923 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1924 sink.para()->text(L"Examples:");
1925 sink.example(L">> MIXER 1 GRID 2");
1928 std::wstring mixer_grid_command(command_context& ctx)
1930 transforms_applier transforms(ctx);
1931 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1932 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1933 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1934 double delta = 1.0 / static_cast<double>(n);
1935 for (int x = 0; x < n; ++x)
1937 for (int y = 0; y < n; ++y)
1939 int index = x + y*n + 1;
1940 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1942 transform.image_transform.fill_translation[0] = x*delta;
1943 transform.image_transform.fill_translation[1] = y*delta;
1944 transform.image_transform.fill_scale[0] = delta;
1945 transform.image_transform.fill_scale[1] = delta;
1946 transform.image_transform.clip_translation[0] = x*delta;
1947 transform.image_transform.clip_translation[1] = y*delta;
1948 transform.image_transform.clip_scale[0] = delta;
1949 transform.image_transform.clip_scale[1] = delta;
1951 }, duration, tween));
1956 return L"202 MIXER OK\r\n";
1959 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1961 sink.short_description(L"Commit all deferred mixer transforms.");
1962 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1963 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1964 sink.para()->text(L"Examples:");
1966 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1967 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1968 L">> MIXER 1 COMMIT");
1971 std::wstring mixer_commit_command(command_context& ctx)
1973 transforms_applier transforms(ctx);
1974 transforms.commit_deferred();
1976 return L"202 MIXER OK\r\n";
1979 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1981 sink.short_description(L"Clear all transformations on a channel or layer.");
1982 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1983 sink.para()->text(L"Clears all transformations on a channel or layer.");
1984 sink.para()->text(L"Examples:");
1985 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1986 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1989 std::wstring mixer_clear_command(command_context& ctx)
1991 int layer = ctx.layer_id;
1994 ctx.channel.channel->stage().clear_transforms();
1996 ctx.channel.channel->stage().clear_transforms(layer);
1998 return L"202 MIXER OK\r\n";
2001 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2003 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2004 sink.syntax(L"CHANNEL_GRID");
2005 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2007 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2008 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2011 std::wstring channel_grid_command(command_context& ctx)
2014 auto self = ctx.channels.back();
2016 core::diagnostics::scoped_call_context save;
2017 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2019 std::vector<std::wstring> params;
2020 params.push_back(L"SCREEN");
2021 params.push_back(L"0");
2022 params.push_back(L"NAME");
2023 params.push_back(L"Channel Grid Window");
2024 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
2026 self.channel->output().add(screen);
2028 for (auto& channel : ctx.channels)
2030 if (channel.channel != self.channel)
2032 core::diagnostics::call_context::for_thread().layer = index;
2033 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2034 self.channel->stage().load(index, producer, false);
2035 self.channel->stage().play(index);
2040 auto num_channels = ctx.channels.size() - 1;
2041 int square_side_length = std::ceil(std::sqrt(num_channels));
2043 ctx.channel_index = self.channel->index();
2045 ctx.parameters.clear();
2046 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2047 mixer_grid_command(ctx);
2049 return L"202 CHANNEL_GRID OK\r\n";
2052 // Thumbnail Commands
2054 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2056 sink.short_description(L"List all thumbnails.");
2057 sink.syntax(L"THUMBNAIL LIST");
2058 sink.para()->text(L"Lists all thumbnails.");
2059 sink.para()->text(L"Examples:");
2061 L">> THUMBNAIL LIST\n"
2062 L"<< 200 THUMBNAIL LIST OK\n"
2063 L"<< \"AMB\" 20130301T124409 1149\n"
2064 L"<< \"foo/bar\" 20130523T234001 244");
2067 std::wstring thumbnail_list_command(command_context& ctx)
2069 std::wstringstream replyString;
2070 replyString << L"200 THUMBNAIL LIST OK\r\n";
2072 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2074 if (boost::filesystem::is_regular_file(itr->path()))
2076 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2079 auto relativePath = get_relative_without_extension(itr->path(), env::thumbnails_folder());
2080 auto str = relativePath.generic_wstring();
2082 if (str[0] == '\\' || str[0] == '/')
2083 str = std::wstring(str.begin() + 1, str.end());
2085 auto mtime = boost::filesystem::last_write_time(itr->path());
2086 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2087 auto file_size = boost::filesystem::file_size(itr->path());
2089 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2093 replyString << L"\r\n";
2095 return boost::to_upper_copy(replyString.str());
2098 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2100 sink.short_description(L"Retrieve a thumbnail.");
2101 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2102 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2103 sink.para()->text(L"Examples:");
2105 L">> THUMBNAIL RETRIEVE foo/bar\n"
2106 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2107 L"<< ...base64 data...");
2110 std::wstring thumbnail_retrieve_command(command_context& ctx)
2112 std::wstring filename = env::thumbnails_folder();
2113 filename.append(ctx.parameters.at(0));
2114 filename.append(L".png");
2116 std::wstring file_contents;
2118 auto found_file = find_case_insensitive(filename);
2121 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2123 if (file_contents.empty())
2124 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2126 std::wstringstream reply;
2128 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2129 reply << file_contents;
2134 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2136 sink.short_description(L"Regenerate a thumbnail.");
2137 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2138 sink.para()->text(L"Regenerates a thumbnail.");
2141 std::wstring thumbnail_generate_command(command_context& ctx)
2145 ctx.thumb_gen->generate(ctx.parameters.at(0));
2146 return L"202 THUMBNAIL GENERATE OK\r\n";
2149 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2152 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2154 sink.short_description(L"Regenerate all thumbnails.");
2155 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2156 sink.para()->text(L"Regenerates all thumbnails.");
2159 std::wstring thumbnail_generateall_command(command_context& ctx)
2163 ctx.thumb_gen->generate_all();
2164 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2167 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2172 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2174 sink.short_description(L"Get information about a media file.");
2175 sink.syntax(L"CINF [filename:string]");
2176 sink.para()->text(L"Returns information about a media file.");
2179 std::wstring cinf_command(command_context& ctx)
2182 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2184 auto path = itr->path();
2185 auto file = path.replace_extension(L"").filename().wstring();
2186 if (boost::iequals(file, ctx.parameters.at(0)))
2187 info += MediaInfo(itr->path(), ctx.media_info_repo);
2191 CASPAR_THROW_EXCEPTION(file_not_found());
2193 std::wstringstream replyString;
2194 replyString << L"200 CINF OK\r\n";
2195 replyString << info << "\r\n";
2197 return replyString.str();
2200 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2202 sink.short_description(L"List all media files.");
2203 sink.syntax(L"CLS");
2205 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2206 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2209 std::wstring cls_command(command_context& ctx)
2211 std::wstringstream replyString;
2212 replyString << L"200 CLS OK\r\n";
2213 replyString << ListMedia(ctx.media_info_repo);
2214 replyString << L"\r\n";
2215 return boost::to_upper_copy(replyString.str());
2218 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2220 sink.short_description(L"List all fonts.");
2221 sink.syntax(L"FLS");
2223 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2224 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2225 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2228 std::wstring fls_command(command_context& ctx)
2230 std::wstringstream replyString;
2231 replyString << L"200 FLS OK\r\n";
2233 for (auto& font : core::text::list_fonts())
2234 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2236 replyString << L"\r\n";
2238 return replyString.str();
2241 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2243 sink.short_description(L"List all templates.");
2244 sink.syntax(L"TLS");
2246 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2247 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2250 std::wstring tls_command(command_context& ctx)
2252 std::wstringstream replyString;
2253 replyString << L"200 TLS OK\r\n";
2255 replyString << ListTemplates(ctx.cg_registry);
2256 replyString << L"\r\n";
2258 return replyString.str();
2261 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2263 sink.short_description(L"Get version information.");
2264 sink.syntax(L"VERSION {[component:string]}");
2265 sink.para()->text(L"Returns the version of specified component.");
2266 sink.para()->text(L"Examples:");
2269 L"<< 201 VERSION OK\n"
2270 L"<< 2.1.0.f207a33 STABLE");
2272 L">> VERSION SERVER\n"
2273 L"<< 201 VERSION OK\n"
2274 L"<< 2.1.0.f207a33 STABLE");
2276 L">> VERSION FLASH\n"
2277 L"<< 201 VERSION OK\n"
2281 std::wstring version_command(command_context& ctx)
2283 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2285 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2287 return L"201 VERSION OK\r\n" + version + L"\r\n";
2290 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2293 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2295 sink.short_description(L"Get a list of the available channels.");
2296 sink.syntax(L"INFO");
2297 sink.para()->text(L"Retrieves a list of the available channels.");
2301 L"<< 1 720p5000 PLAYING\n"
2302 L"<< 2 PAL PLAYING");
2305 std::wstring info_command(command_context& ctx)
2307 std::wstringstream replyString;
2308 // This is needed for backwards compatibility with old clients
2309 replyString << L"200 INFO OK\r\n";
2310 for (size_t n = 0; n < ctx.channels.size(); ++n)
2311 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2312 replyString << L"\r\n";
2313 return replyString.str();
2316 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2318 std::wstringstream replyString;
2320 if (command.empty())
2321 replyString << L"201 INFO OK\r\n";
2323 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2325 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2326 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2327 replyString << L"\r\n";
2328 return replyString.str();
2331 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2333 sink.short_description(L"Get information about a channel or a layer.");
2334 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2335 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2336 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2339 std::wstring info_channel_command(command_context& ctx)
2341 boost::property_tree::wptree info;
2342 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2344 if (layer == std::numeric_limits<int>::min())
2346 info.add_child(L"channel", ctx.channel.channel->info())
2347 .add(L"index", ctx.channel_index);
2351 if (ctx.parameters.size() >= 1)
2353 if (boost::iequals(ctx.parameters.at(0), L"B"))
2354 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2356 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2360 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2364 return create_info_xml_reply(info);
2367 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2369 sink.short_description(L"Get information about a template.");
2370 sink.syntax(L"INFO TEMPLATE [template:string]");
2371 sink.para()->text(L"Gets information about the specified template.");
2374 std::wstring info_template_command(command_context& ctx)
2376 auto filename = ctx.parameters.at(0);
2378 std::wstringstream str;
2379 str << u16(ctx.cg_registry->read_meta_info(filename));
2380 boost::property_tree::wptree info;
2381 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2383 return create_info_xml_reply(info, L"TEMPLATE");
2386 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2388 sink.short_description(L"Get the contents of the configuration used.");
2389 sink.syntax(L"INFO CONFIG");
2390 sink.para()->text(L"Gets the contents of the configuration used.");
2393 std::wstring info_config_command(command_context& ctx)
2395 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2398 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2400 sink.short_description(L"Get information about the paths used.");
2401 sink.syntax(L"INFO PATHS");
2402 sink.para()->text(L"Gets information about the paths used.");
2405 std::wstring info_paths_command(command_context& ctx)
2407 boost::property_tree::wptree info;
2408 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2409 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2411 return create_info_xml_reply(info, L"PATHS");
2414 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2416 sink.short_description(L"Get system information.");
2417 sink.syntax(L"INFO SYSTEM");
2418 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2421 std::wstring info_system_command(command_context& ctx)
2423 boost::property_tree::wptree info;
2425 info.add(L"system.name", caspar::system_product_name());
2426 info.add(L"system.os.description", caspar::os_description());
2427 info.add(L"system.cpu", caspar::cpu_info());
2429 ctx.system_info_repo->fill_information(info);
2431 return create_info_xml_reply(info, L"SYSTEM");
2434 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2436 sink.short_description(L"Get detailed information about all channels.");
2437 sink.syntax(L"INFO SERVER");
2438 sink.para()->text(L"Gets detailed information about all channels.");
2441 std::wstring info_server_command(command_context& ctx)
2443 boost::property_tree::wptree info;
2446 for (auto& channel : ctx.channels)
2447 info.add_child(L"channels.channel", channel.channel->info())
2448 .add(L"index", ++index);
2450 return create_info_xml_reply(info, L"SERVER");
2453 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2455 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2456 sink.syntax(L"INFO QUEUES");
2457 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2460 std::wstring info_queues_command(command_context& ctx)
2462 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2465 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2467 sink.short_description(L"Lists all known threads in the server.");
2468 sink.syntax(L"INFO THREADS");
2469 sink.para()->text(L"Lists all known threads in the server.");
2472 std::wstring info_threads_command(command_context& ctx)
2474 std::wstringstream replyString;
2475 replyString << L"200 INFO THREADS OK\r\n";
2477 for (auto& thread : get_thread_infos())
2479 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2482 replyString << L"\r\n";
2483 return replyString.str();
2486 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2488 sink.short_description(L"Get the current delay on a channel or a layer.");
2489 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2490 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2493 std::wstring info_delay_command(command_context& ctx)
2495 boost::property_tree::wptree info;
2496 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2498 if (layer == std::numeric_limits<int>::min())
2499 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2501 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2502 .add(L"index", layer);
2504 return create_info_xml_reply(info, L"DELAY");
2507 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2509 sink.short_description(L"Open the diagnostics window.");
2510 sink.syntax(L"DIAG");
2511 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2514 std::wstring diag_command(command_context& ctx)
2516 core::diagnostics::osd::show_graphs(true);
2518 return L"202 DIAG OK\r\n";
2521 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2523 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2524 sink.syntax(L"GL INFO");
2525 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2528 std::wstring gl_info_command(command_context& ctx)
2530 auto device = ctx.ogl_device;
2533 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2535 std::wstringstream result;
2536 result << L"201 GL INFO OK\r\n";
2538 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2539 auto info = device->info();
2540 boost::property_tree::write_xml(result, info, w);
2543 return result.str();
2546 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2548 sink.short_description(L"Release pooled OpenGL resources.");
2549 sink.syntax(L"GL GC");
2550 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2553 std::wstring gl_gc_command(command_context& ctx)
2555 auto device = ctx.ogl_device;
2558 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2560 device->gc().wait();
2562 return L"202 GL GC OK\r\n";
2565 static const int WIDTH = 80;
2567 struct max_width_sink : public core::help_sink
2569 std::size_t max_width = 0;
2571 void begin_item(const std::wstring& name) override
2573 max_width = std::max(name.length(), max_width);
2577 struct short_description_sink : public core::help_sink
2580 std::wstringstream& out;
2582 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2584 void begin_item(const std::wstring& name) override
2586 out << std::left << std::setw(width + 1) << name;
2589 void short_description(const std::wstring& short_description) override
2591 out << short_description << L"\r\n";
2595 struct simple_paragraph_builder : core::paragraph_builder
2597 std::wostringstream out;
2598 std::wstringstream& commit_to;
2600 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2601 ~simple_paragraph_builder()
2603 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2605 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2607 out << std::move(text);
2608 return shared_from_this();
2610 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2611 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2612 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2613 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2616 struct simple_definition_list_builder : core::definition_list_builder
2618 std::wstringstream& out;
2620 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2621 ~simple_definition_list_builder()
2626 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2628 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2629 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2630 return shared_from_this();
2634 struct long_description_sink : public core::help_sink
2636 std::wstringstream& out;
2638 long_description_sink(std::wstringstream& out) : out(out) { }
2640 void syntax(const std::wstring& syntax) override
2642 out << L"Syntax:\n";
2643 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2646 spl::shared_ptr<core::paragraph_builder> para() override
2648 return spl::make_shared<simple_paragraph_builder>(out);
2651 spl::shared_ptr<core::definition_list_builder> definitions() override
2653 return spl::make_shared<simple_definition_list_builder>(out);
2656 void example(const std::wstring& code, const std::wstring& caption) override
2658 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2660 if (!caption.empty())
2661 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2666 void begin_item(const std::wstring& name) override
2668 out << name << L"\n\n";
2672 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2674 std::wstringstream result;
2675 result << L"200 " << help_command << L" OK\r\n";
2676 max_width_sink width;
2677 ctx.help_repo->help(tags, width);
2678 short_description_sink sink(width.max_width, result);
2679 sink.width = width.max_width;
2680 ctx.help_repo->help(tags, sink);
2682 return result.str();
2685 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2687 std::wstringstream result;
2688 result << L"201 " << help_command << L" OK\r\n";
2689 auto joined = boost::join(ctx.parameters, L" ");
2690 long_description_sink sink(result);
2691 ctx.help_repo->help(tags, joined, sink);
2693 return result.str();
2696 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2698 sink.short_description(L"Show online help for AMCP commands.");
2699 sink.syntax(LR"(HELP {[command:string]})");
2700 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2701 sink.example(L">> HELP", L"Shows a list of commands.");
2702 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2705 std::wstring help_command(command_context& ctx)
2707 if (ctx.parameters.size() == 0)
2708 return create_help_list(L"HELP", ctx, { L"AMCP" });
2710 return create_help_details(L"HELP", ctx, { L"AMCP" });
2713 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2715 sink.short_description(L"Show online help for producers.");
2716 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2717 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2718 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2719 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2722 std::wstring help_producer_command(command_context& ctx)
2724 if (ctx.parameters.size() == 0)
2725 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2727 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2730 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2732 sink.short_description(L"Show online help for consumers.");
2733 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2734 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2735 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2736 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2739 std::wstring help_consumer_command(command_context& ctx)
2741 if (ctx.parameters.size() == 0)
2742 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2744 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2747 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2749 sink.short_description(L"Disconnect the session.");
2750 sink.syntax(L"BYE");
2752 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2753 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2756 std::wstring bye_command(command_context& ctx)
2758 ctx.client->disconnect();
2762 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2764 sink.short_description(L"Shutdown the server.");
2765 sink.syntax(L"KILL");
2766 sink.para()->text(L"Shuts the server down.");
2769 std::wstring kill_command(command_context& ctx)
2771 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2772 return L"202 KILL OK\r\n";
2775 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2777 sink.short_description(L"Shutdown the server with restart exit code.");
2778 sink.syntax(L"RESTART");
2780 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2781 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2784 std::wstring restart_command(command_context& ctx)
2786 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2787 return L"202 RESTART OK\r\n";
2790 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2792 sink.short_description(L"Lock or unlock access to a channel.");
2793 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2794 sink.para()->text(L"Allows for exclusive access to a channel.");
2795 sink.para()->text(L"Examples:");
2796 sink.example(L"LOCK 1 ACQUIRE secret");
2797 sink.example(L"LOCK 1 RELEASE");
2798 sink.example(L"LOCK 1 CLEAR");
2801 std::wstring lock_command(command_context& ctx)
2803 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2804 auto lock = ctx.channels.at(channel_index).lock;
2805 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2807 if (command == L"ACQUIRE")
2809 std::wstring lock_phrase = ctx.parameters.at(2);
2811 //TODO: read options
2813 //just lock one channel
2814 if (!lock->try_lock(lock_phrase, ctx.client))
2815 return L"503 LOCK ACQUIRE FAILED\r\n";
2817 return L"202 LOCK ACQUIRE OK\r\n";
2819 else if (command == L"RELEASE")
2821 lock->release_lock(ctx.client);
2822 return L"202 LOCK RELEASE OK\r\n";
2824 else if (command == L"CLEAR")
2826 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2827 std::wstring client_override_phrase;
2829 if (!override_phrase.empty())
2830 client_override_phrase = ctx.parameters.at(2);
2832 //just clear one channel
2833 if (client_override_phrase != override_phrase)
2834 return L"503 LOCK CLEAR FAILED\r\n";
2836 lock->clear_locks();
2838 return L"202 LOCK CLEAR OK\r\n";
2841 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2844 void register_commands(amcp_command_repository& repo)
2846 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2847 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2848 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2849 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2850 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2851 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2852 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2853 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2854 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2855 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2856 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2857 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2858 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2859 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2860 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2862 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2863 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2864 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2865 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2867 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2868 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2869 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2870 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2871 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2872 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2873 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2874 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2875 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2877 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2878 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2879 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2880 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2881 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2882 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2883 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2884 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2885 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2886 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2887 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2888 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2889 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2890 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2891 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2892 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2893 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2894 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2895 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2896 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2897 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2898 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2900 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2901 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2902 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2903 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2905 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2906 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2907 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
2908 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2909 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2910 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2911 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2912 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2913 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2914 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2915 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2916 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2917 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2918 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2919 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2920 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2921 repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
2922 repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
2923 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2924 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2925 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2926 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2927 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2928 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2932 }} //namespace caspar