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).get();
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;
549 replyString << L"202 CALL OK\r\n";
551 replyString << L"201 CALL OK\r\n" << result << 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_index: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".");
610 ->text(L"Specifying ")->code(L"consumer_index")
611 ->text(L" overrides the index that the consumer itself decides and can later be used with the ")
612 ->see(L"REMOVE")->text(L" command to remove the consumer.");
613 sink.para()->text(L"Examples:");
614 sink.example(L">> ADD 1 DECKLINK 1");
615 sink.example(L">> ADD 1 BLUEFISH 2");
616 sink.example(L">> ADD 1 SCREEN");
617 sink.example(L">> ADD 1 AUDIO");
618 sink.example(L">> ADD 1 IMAGE filename");
619 sink.example(L">> ADD 1 FILE filename.mov");
620 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
622 L">> ADD 1-700 FILE filename.mov SEPARATE_KEY\n"
623 L">> REMOVE 1-700", L"overriding the consumer index to easier remove later.");
624 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
625 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
628 std::wstring add_command(command_context& ctx)
630 replace_placeholders(
631 L"<CLIENT_IP_ADDRESS>",
632 ctx.client->address(),
635 core::diagnostics::scoped_call_context save;
636 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
638 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
639 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
641 return L"202 ADD OK\r\n";
644 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
646 sink.short_description(L"Remove a consumer from a video channel.");
647 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
649 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
650 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
651 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
652 sink.para()->text(L"Examples:");
653 sink.example(L">> REMOVE 1 DECKLINK 1");
654 sink.example(L">> REMOVE 1 BLUEFISH 2");
655 sink.example(L">> REMOVE 1 SCREEN");
656 sink.example(L">> REMOVE 1 AUDIO");
657 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
660 std::wstring remove_command(command_context& ctx)
662 auto index = ctx.layer_index(std::numeric_limits<int>::min());
664 if (index == std::numeric_limits<int>::min())
666 replace_placeholders(
667 L"<CLIENT_IP_ADDRESS>",
668 ctx.client->address(),
671 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
674 ctx.channel.channel->output().remove(index);
676 return L"202 REMOVE OK\r\n";
679 void print_describer(core::help_sink& sink, const core::help_repository& repo)
681 sink.short_description(L"Take a snapshot of a channel.");
682 sink.syntax(L"PRINT [video_channel:int]");
684 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
685 ->code(L"media")->text(L" folder.");
686 sink.para()->text(L"Examples:");
687 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
690 std::wstring print_command(command_context& ctx)
692 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
694 return L"202 PRINT OK\r\n";
697 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
699 sink.short_description(L"Change the log level of the server.");
700 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
701 sink.para()->text(L"Changes the log level of the server.");
702 sink.para()->text(L"Examples:");
703 sink.example(L">> LOG LEVEL trace");
704 sink.example(L">> LOG LEVEL info");
707 std::wstring log_level_command(command_context& ctx)
709 log::set_log_level(ctx.parameters.at(0));
711 return L"202 LOG OK\r\n";
714 void log_category_describer(core::help_sink& sink, const core::help_repository& repo)
716 sink.short_description(L"Enable/disable a logging category in the server.");
717 sink.syntax(L"LOG CATEGORY [category:calltrace,communication] [enable:0,1]");
718 sink.para()->text(L"Enables or disables the specified logging category.");
719 sink.para()->text(L"Examples:");
720 sink.example(L">> LOG CATEGORY calltrace 1", L"to enable call trace");
721 sink.example(L">> LOG CATEGORY calltrace 0", L"to disable call trace");
724 std::wstring log_category_command(command_context& ctx)
726 log::set_log_category(ctx.parameters.at(0), ctx.parameters.at(1) == L"1");
728 return L"202 LOG OK\r\n";
731 void set_describer(core::help_sink& sink, const core::help_repository& repo)
733 sink.short_description(L"Change the value of a channel variable.");
734 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
735 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
737 ->item(L"MODE", L"Changes the video format of the channel.")
738 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
739 sink.para()->text(L"Examples:");
740 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
741 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
744 std::wstring set_command(command_context& ctx)
746 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
747 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
751 auto format_desc = core::video_format_desc(value);
752 if (format_desc.format != core::video_format::invalid)
754 ctx.channel.channel->video_format_desc(format_desc);
755 return L"202 SET MODE OK\r\n";
758 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video mode"));
760 else if (name == L"CHANNEL_LAYOUT")
762 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
766 ctx.channel.channel->audio_channel_layout(*channel_layout);
767 return L"202 SET CHANNEL_LAYOUT OK\r\n";
770 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid audio channel layout"));
773 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid channel variable"));
776 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
778 sink.short_description(L"Store a dataset.");
779 sink.syntax(L"DATA STORE [name:string] [data:string]");
780 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
781 sink.para()->text(L"Directories will be created if they do not exist.");
782 sink.para()->text(L"Examples:");
783 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
784 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
787 std::wstring data_store_command(command_context& ctx)
789 std::wstring filename = env::data_folder();
790 filename.append(ctx.parameters[0]);
791 filename.append(L".ftd");
793 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
794 auto found_data_path = find_case_insensitive(data_path);
797 data_path = *found_data_path;
799 if (!boost::filesystem::exists(data_path))
800 boost::filesystem::create_directories(data_path);
802 auto found_filename = find_case_insensitive(filename);
805 filename = *found_filename; // Overwrite case insensitive.
807 boost::filesystem::wofstream datafile(filename);
809 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
811 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
812 datafile << ctx.parameters[1] << std::flush;
815 return L"202 DATA STORE OK\r\n";
818 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
820 sink.short_description(L"Retrieve a dataset.");
821 sink.syntax(L"DATA RETRIEVE [name:string]");
822 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
823 sink.para()->text(L"Examples:");
824 sink.example(L">> DATA RETRIEVE my_data");
825 sink.example(L">> DATA RETRIEVE Folder1/my_data");
828 std::wstring data_retrieve_command(command_context& ctx)
830 std::wstring filename = env::data_folder();
831 filename.append(ctx.parameters[0]);
832 filename.append(L".ftd");
834 std::wstring file_contents;
836 auto found_file = find_case_insensitive(filename);
839 file_contents = read_file(boost::filesystem::path(*found_file));
841 if (file_contents.empty())
842 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
844 std::wstringstream reply;
845 reply << L"201 DATA RETRIEVE OK\r\n";
847 std::wstringstream file_contents_stream(file_contents);
850 bool firstLine = true;
851 while (std::getline(file_contents_stream, line))
865 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
867 sink.short_description(L"List stored datasets.");
868 sink.syntax(L"DATA LIST");
869 sink.para()->text(L"Returns a list of all stored datasets.");
872 std::wstring data_list_command(command_context& ctx)
874 std::wstringstream replyString;
875 replyString << L"200 DATA LIST OK\r\n";
877 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
879 if (boost::filesystem::is_regular_file(itr->path()))
881 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
884 auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
885 auto str = relativePath.generic_wstring();
887 if (str[0] == L'\\' || str[0] == L'/')
888 str = std::wstring(str.begin() + 1, str.end());
890 replyString << str << L"\r\n";
894 replyString << L"\r\n";
896 return boost::to_upper_copy(replyString.str());
899 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
901 sink.short_description(L"Remove a stored dataset.");
902 sink.syntax(L"DATA REMOVE [name:string]");
903 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
904 sink.para()->text(L"Examples:");
905 sink.example(L">> DATA REMOVE my_data");
906 sink.example(L">> DATA REMOVE Folder1/my_data");
909 std::wstring data_remove_command(command_context& ctx)
911 std::wstring filename = env::data_folder();
912 filename.append(ctx.parameters[0]);
913 filename.append(L".ftd");
915 if (!boost::filesystem::exists(filename))
916 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
918 if (!boost::filesystem::remove(filename))
919 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
921 return L"202 DATA REMOVE OK\r\n";
924 // Template Graphics Commands
926 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
928 sink.short_description(L"Prepare a template for displaying.");
929 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
931 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
932 ->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.");
933 sink.para()->text(L"Examples:");
934 sink.example(L"CG 1 ADD 10 svtnews/info 1");
937 std::wstring cg_add_command(command_context& ctx)
939 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
941 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
942 std::wstring label; //_parameters[2]
943 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
944 unsigned int dataIndex = 3;
946 if (ctx.parameters.at(2).length() > 1)
948 label = ctx.parameters.at(2);
951 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
952 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
955 { //read play-on-load-flag
956 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
959 const wchar_t* pDataString = 0;
960 std::wstring dataFromFile;
961 if (ctx.parameters.size() > dataIndex)
963 const std::wstring& dataString = ctx.parameters.at(dataIndex);
965 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
966 pDataString = dataString.c_str();
969 //The data is not an XML-string, it must be a filename
970 std::wstring filename = env::data_folder();
971 filename.append(dataString);
972 filename.append(L".ftd");
974 auto found_file = find_case_insensitive(filename);
978 dataFromFile = read_file(boost::filesystem::path(*found_file));
979 pDataString = dataFromFile.c_str();
984 auto filename = ctx.parameters.at(1);
985 auto proxy = ctx.cg_registry->get_or_create_proxy(
986 spl::make_shared_ptr(ctx.channel.channel),
987 get_producer_dependencies(ctx.channel.channel, ctx),
988 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
991 if (proxy == core::cg_proxy::empty())
992 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
994 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
996 return L"202 CG OK\r\n";
999 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
1001 sink.short_description(L"Play and display a template.");
1002 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
1003 sink.para()->text(L"Plays and displays the template in the specified layer.");
1004 sink.para()->text(L"Examples:");
1005 sink.example(L"CG 1 PLAY 0");
1008 std::wstring cg_play_command(command_context& ctx)
1010 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1011 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1013 return L"202 CG OK\r\n";
1016 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1018 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1020 if (proxy == cg_proxy::empty())
1021 CASPAR_THROW_EXCEPTION(expected_user_error() << msg_info(L"No CG proxy running on layer"));
1026 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1028 sink.short_description(L"Stop and remove a template.");
1029 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1031 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1032 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1033 sink.para()->text(L"Examples:");
1034 sink.example(L"CG 1 STOP 0");
1037 std::wstring cg_stop_command(command_context& ctx)
1039 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1040 get_expected_cg_proxy(ctx)->stop(layer, 0);
1042 return L"202 CG OK\r\n";
1045 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1047 sink.short_description(LR"(Trigger a "continue" in a template.)");
1048 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1050 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1051 ->text(L"This is used to control animations that has multiple discreet steps.");
1052 sink.para()->text(L"Examples:");
1053 sink.example(L"CG 1 NEXT 0");
1056 std::wstring cg_next_command(command_context& ctx)
1058 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1059 get_expected_cg_proxy(ctx)->next(layer);
1061 return L"202 CG OK\r\n";
1064 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1066 sink.short_description(L"Remove a template.");
1067 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1068 sink.para()->text(L"Removes the template from the specified layer.");
1069 sink.para()->text(L"Examples:");
1070 sink.example(L"CG 1 REMOVE 0");
1073 std::wstring cg_remove_command(command_context& ctx)
1075 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1076 get_expected_cg_proxy(ctx)->remove(layer);
1078 return L"202 CG OK\r\n";
1081 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1083 sink.short_description(L"Remove all templates on a video layer.");
1084 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1085 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1086 sink.para()->text(L"Examples:");
1087 sink.example(L"CG 1 CLEAR");
1090 std::wstring cg_clear_command(command_context& ctx)
1092 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1094 return L"202 CG OK\r\n";
1097 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1099 sink.short_description(L"Update a template with new data.");
1100 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1101 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.");
1104 std::wstring cg_update_command(command_context& ctx)
1106 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1108 std::wstring dataString = ctx.parameters.at(1);
1109 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1111 //The data is not XML or Json, it must be a filename
1112 std::wstring filename = env::data_folder();
1113 filename.append(dataString);
1114 filename.append(L".ftd");
1116 dataString = read_file(boost::filesystem::path(filename));
1119 get_expected_cg_proxy(ctx)->update(layer, dataString);
1121 return L"202 CG OK\r\n";
1124 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1126 sink.short_description(L"Invoke a method/label on a template.");
1127 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1128 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1129 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1132 std::wstring cg_invoke_command(command_context& ctx)
1134 std::wstringstream replyString;
1135 replyString << L"201 CG OK\r\n";
1136 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1137 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1138 replyString << result << L"\r\n";
1140 return replyString.str();
1143 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1145 sink.short_description(L"Get information about a running template or the template host.");
1146 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1147 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1148 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1151 std::wstring cg_info_command(command_context& ctx)
1153 std::wstringstream replyString;
1154 replyString << L"201 CG OK\r\n";
1156 if (ctx.parameters.empty())
1158 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1159 replyString << info << L"\r\n";
1163 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1164 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1166 replyString << desc << L"\r\n";
1169 return replyString.str();
1174 core::frame_transform get_current_transform(command_context& ctx)
1176 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1179 template<typename Func>
1180 std::wstring reply_value(command_context& ctx, const Func& extractor)
1182 auto value = extractor(get_current_transform(ctx));
1184 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1187 class transforms_applier
1189 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1191 std::vector<stage::transform_tuple_t> transforms_;
1192 command_context& ctx_;
1195 transforms_applier(command_context& ctx)
1198 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1201 ctx.parameters.pop_back();
1204 void add(stage::transform_tuple_t&& transform)
1206 transforms_.push_back(std::move(transform));
1209 void commit_deferred()
1211 auto& transforms = deferred_transforms_[ctx_.channel_index];
1212 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1220 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1221 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1224 ctx_.channel.channel->stage().apply_transforms(transforms_);
1227 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1229 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1231 sink.short_description(L"Let a layer act as alpha for the one obove.");
1232 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1234 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1235 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1236 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1237 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1238 ->text(L"instead it will be used as the key for the layer above.");
1239 sink.para()->text(L"Examples:");
1240 sink.example(L">> MIXER 1-0 KEYER 1");
1242 L">> MIXER 1-0 KEYER\n"
1243 L"<< 201 MIXER OK\n"
1244 L"<< 1", L"to retrieve the current state");
1247 std::wstring mixer_keyer_command(command_context& ctx)
1249 if (ctx.parameters.empty())
1250 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1252 transforms_applier transforms(ctx);
1253 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1254 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1256 transform.image_transform.is_key = value;
1258 }, 0, tweener(L"linear")));
1261 return L"202 MIXER OK\r\n";
1264 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1266 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1268 sink.short_description(L"Enable chroma keying on a layer.");
1269 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1271 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1272 sink.para()->text(L"Examples:");
1273 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1274 sink.example(L">> MIXER 1-1 CHROMA none");
1276 L">> MIXER 1-1 BLEND\n"
1277 L"<< 201 MIXER OK\n"
1278 L"<< SCREEN", L"for getting the current blend mode");
1281 std::wstring mixer_chroma_command(command_context& ctx)
1283 if (ctx.parameters.empty())
1285 auto chroma = get_current_transform(ctx).image_transform.chroma;
1286 return L"201 MIXER OK\r\n"
1287 + core::get_chroma_mode(chroma.key) + L" "
1288 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1289 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1290 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1293 transforms_applier transforms(ctx);
1294 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1295 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1297 core::chroma chroma;
1298 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1300 if (chroma.key != core::chroma::type::none)
1302 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1303 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1304 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1307 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1309 transform.image_transform.chroma = chroma;
1311 }, duration, tween));
1314 return L"202 MIXER OK\r\n";
1317 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1319 sink.short_description(L"Set the blend mode for a layer.");
1320 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1322 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1323 ->text(L"If no argument is given the current blend mode is returned.");
1325 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1326 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1327 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1328 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1329 sink.para()->text(L"Examples:");
1330 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1332 L">> MIXER 1-1 BLEND\n"
1333 L"<< 201 MIXER OK\n"
1334 L"<< SCREEN", L"for getting the current blend mode");
1335 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1338 std::wstring mixer_blend_command(command_context& ctx)
1340 if (ctx.parameters.empty())
1341 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1343 transforms_applier transforms(ctx);
1344 auto value = get_blend_mode(ctx.parameters.at(0));
1345 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1347 transform.image_transform.blend_mode = value;
1349 }, 0, tweener(L"linear")));
1352 return L"202 MIXER OK\r\n";
1355 template<typename Getter, typename Setter>
1356 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1358 if (ctx.parameters.empty())
1359 return reply_value(ctx, getter);
1361 transforms_applier transforms(ctx);
1362 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1363 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1364 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1366 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1368 setter(transform, value);
1370 }, duration, tween));
1373 return L"202 MIXER OK\r\n";
1376 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1378 sink.short_description(L"Change the opacity of a layer.");
1379 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1380 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1381 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1382 sink.para()->text(L"Examples:");
1383 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1385 L">> MIXER 1-0 OPACITY\n"
1386 L"<< 201 MIXER OK\n"
1387 L"<< 0.5", L"to retrieve the current opacity");
1390 std::wstring mixer_opacity_command(command_context& ctx)
1392 return single_double_animatable_mixer_command(
1394 [](const frame_transform& t) { return t.image_transform.opacity; },
1395 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1398 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1400 sink.short_description(L"Change the brightness of a layer.");
1401 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1402 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1403 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1404 sink.para()->text(L"Examples:");
1405 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1407 L">> MIXER 1-0 BRIGHTNESS\n"
1408 L"<< 201 MIXER OK\n"
1409 L"0.5", L"to retrieve the current brightness");
1412 std::wstring mixer_brightness_command(command_context& ctx)
1414 return single_double_animatable_mixer_command(
1416 [](const frame_transform& t) { return t.image_transform.brightness; },
1417 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1420 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1422 sink.short_description(L"Change the saturation of a layer.");
1423 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1424 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1425 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1426 sink.para()->text(L"Examples:");
1427 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1429 L">> MIXER 1-0 SATURATION\n"
1430 L"<< 201 MIXER OK\n"
1431 L"<< 0.5", L"to retrieve the current saturation");
1434 std::wstring mixer_saturation_command(command_context& ctx)
1436 return single_double_animatable_mixer_command(
1438 [](const frame_transform& t) { return t.image_transform.saturation; },
1439 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1442 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1444 sink.short_description(L"Change the contrast of a layer.");
1445 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1446 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1447 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1448 sink.para()->text(L"Examples:");
1449 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1451 L">> MIXER 1-0 CONTRAST\n"
1452 L"<< 201 MIXER OK\n"
1453 L"<< 0.5", L"to retrieve the current contrast");
1456 std::wstring mixer_contrast_command(command_context& ctx)
1458 return single_double_animatable_mixer_command(
1460 [](const frame_transform& t) { return t.image_transform.contrast; },
1461 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1464 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1466 sink.short_description(L"Adjust the video levels of a layer.");
1467 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);
1469 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1471 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1472 ->item(L"gamma", L"Adjusts the gamma of the image.")
1473 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1474 sink.para()->text(L"Examples:");
1475 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");
1476 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");
1477 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1479 L">> MIXER 1-10 LEVELS\n"
1480 L"<< 201 MIXER OK\n"
1481 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1484 std::wstring mixer_levels_command(command_context& ctx)
1486 if (ctx.parameters.empty())
1488 auto levels = get_current_transform(ctx).image_transform.levels;
1489 return L"201 MIXER OK\r\n"
1490 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1491 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1492 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1493 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1494 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1497 transforms_applier transforms(ctx);
1499 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1500 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1501 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1502 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1503 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1504 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1505 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1507 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1509 transform.image_transform.levels = value;
1511 }, duration, tween));
1514 return L"202 MIXER OK\r\n";
1517 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1519 sink.short_description(L"Change the fill position and scale of a layer.");
1520 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1522 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1523 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1524 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1525 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1527 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1528 ->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.");
1529 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1531 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1532 ->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, ")
1533 ->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");
1535 ->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.")
1536 ->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.")
1537 ->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.")
1538 ->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.");
1539 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1540 sink.para()->text(L"Examples:");
1541 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1543 L">> MIXER 1-0 FILL\n"
1544 L"<< 201 MIXER OK\n"
1545 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1548 std::wstring mixer_fill_command(command_context& ctx)
1550 if (ctx.parameters.empty())
1552 auto transform = get_current_transform(ctx).image_transform;
1553 auto translation = transform.fill_translation;
1554 auto scale = transform.fill_scale;
1555 return L"201 MIXER OK\r\n"
1556 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1557 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1558 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1559 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1562 transforms_applier transforms(ctx);
1563 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1564 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1565 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1566 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1567 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1568 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1570 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1572 transform.image_transform.fill_translation[0] = x;
1573 transform.image_transform.fill_translation[1] = y;
1574 transform.image_transform.fill_scale[0] = x_s;
1575 transform.image_transform.fill_scale[1] = y_s;
1577 }, duration, tween));
1580 return L"202 MIXER OK\r\n";
1583 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1585 sink.short_description(L"Change the clipping viewport of a layer.");
1586 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1588 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1589 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1590 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1592 ->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.")
1593 ->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.")
1594 ->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.")
1595 ->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.");
1596 sink.para()->text(L"Examples:");
1597 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1599 L">> MIXER 1-0 CLIP\n"
1600 L"<< 201 MIXER OK\n"
1601 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1604 std::wstring mixer_clip_command(command_context& ctx)
1606 if (ctx.parameters.empty())
1608 auto transform = get_current_transform(ctx).image_transform;
1609 auto translation = transform.clip_translation;
1610 auto scale = transform.clip_scale;
1612 return L"201 MIXER OK\r\n"
1613 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1614 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1615 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1616 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1619 transforms_applier transforms(ctx);
1620 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1621 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1622 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1623 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1624 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1625 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1627 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1629 transform.image_transform.clip_translation[0] = x;
1630 transform.image_transform.clip_translation[1] = y;
1631 transform.image_transform.clip_scale[0] = x_s;
1632 transform.image_transform.clip_scale[1] = y_s;
1634 }, duration, tween));
1637 return L"202 MIXER OK\r\n";
1640 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1642 sink.short_description(L"Change the anchor point of a layer.");
1643 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1644 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1646 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1647 ->text(L" will be done from.");
1649 ->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.")
1650 ->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.");
1651 sink.para()->text(L"Examples:");
1652 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1654 L">> MIXER 1-10 ANCHOR\n"
1655 L"<< 201 MIXER OK\n"
1656 L"<< 0.5 0.6", L"gets the anchor point");
1659 std::wstring mixer_anchor_command(command_context& ctx)
1661 if (ctx.parameters.empty())
1663 auto transform = get_current_transform(ctx).image_transform;
1664 auto anchor = transform.anchor;
1665 return L"201 MIXER OK\r\n"
1666 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1667 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1670 transforms_applier transforms(ctx);
1671 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1672 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1673 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1674 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1676 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1678 transform.image_transform.anchor[0] = x;
1679 transform.image_transform.anchor[1] = y;
1681 }, duration, tween));
1684 return L"202 MIXER OK\r\n";
1687 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1689 sink.short_description(L"Crop a layer.");
1690 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);
1692 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1693 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1694 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1696 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1697 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1698 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1699 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1700 sink.para()->text(L"Examples:");
1701 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");
1703 L">> MIXER 1-0 CROP\n"
1704 L"<< 201 MIXER OK\n"
1705 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1708 std::wstring mixer_crop_command(command_context& ctx)
1710 if (ctx.parameters.empty())
1712 auto crop = get_current_transform(ctx).image_transform.crop;
1713 return L"201 MIXER OK\r\n"
1714 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1715 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1716 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1717 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1720 transforms_applier transforms(ctx);
1721 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1722 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1723 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1724 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1725 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1726 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1728 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1730 transform.image_transform.crop.ul[0] = ul_x;
1731 transform.image_transform.crop.ul[1] = ul_y;
1732 transform.image_transform.crop.lr[0] = lr_x;
1733 transform.image_transform.crop.lr[1] = lr_y;
1735 }, duration, tween));
1738 return L"202 MIXER OK\r\n";
1741 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1743 sink.short_description(L"Rotate a layer.");
1744 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1746 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1747 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1748 sink.para()->text(L"Examples:");
1749 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1751 L">> MIXER 1-0 ROTATION\n"
1752 L"<< 201 MIXER OK\n"
1753 L"<< 45", L"to retrieve the current angle");
1756 std::wstring mixer_rotation_command(command_context& ctx)
1758 static const double PI = 3.141592653589793;
1760 return single_double_animatable_mixer_command(
1762 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1763 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1766 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1768 sink.short_description(L"Adjust the perspective transform of a layer.");
1769 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);
1771 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1773 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1774 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1775 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1776 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1777 sink.para()->text(L"Examples:");
1778 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1780 L">> MIXER 1-10 PERSPECTIVE\n"
1781 L"<< 201 MIXER OK\n"
1782 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1785 std::wstring mixer_perspective_command(command_context& ctx)
1787 if (ctx.parameters.empty())
1789 auto perspective = get_current_transform(ctx).image_transform.perspective;
1792 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1793 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1794 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1795 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1796 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1797 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1798 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1799 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1802 transforms_applier transforms(ctx);
1803 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1804 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1805 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1806 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1807 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1808 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1809 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1810 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1811 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1812 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1814 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1816 transform.image_transform.perspective.ul[0] = ul_x;
1817 transform.image_transform.perspective.ul[1] = ul_y;
1818 transform.image_transform.perspective.ur[0] = ur_x;
1819 transform.image_transform.perspective.ur[1] = ur_y;
1820 transform.image_transform.perspective.lr[0] = lr_x;
1821 transform.image_transform.perspective.lr[1] = lr_y;
1822 transform.image_transform.perspective.ll[0] = ll_x;
1823 transform.image_transform.perspective.ll[1] = ll_y;
1825 }, duration, tween));
1828 return L"202 MIXER OK\r\n";
1831 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1833 sink.short_description(L"Enable or disable mipmapping for a layer.");
1834 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1836 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1837 ->text(L"If no argument is given the current state is returned.");
1838 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1839 sink.para()->text(L"Examples:");
1840 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1842 L">> MIXER 1-10 MIPMAP\n"
1843 L"<< 201 MIXER OK\n"
1844 L"<< 1", L"for getting the current state");
1847 std::wstring mixer_mipmap_command(command_context& ctx)
1849 if (ctx.parameters.empty())
1850 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1852 transforms_applier transforms(ctx);
1853 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1854 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1856 transform.image_transform.use_mipmap = value;
1858 }, 0, tweener(L"linear")));
1861 return L"202 MIXER OK\r\n";
1864 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1866 sink.short_description(L"Change the volume of a layer.");
1867 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1868 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1869 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1870 sink.para()->text(L"Examples:");
1871 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1872 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1874 L">> MIXER 1-0 VOLUME\n"
1875 L"<< 201 MIXER OK\n"
1876 L"<< 0.8", L"to retrieve the current volume");
1879 std::wstring mixer_volume_command(command_context& ctx)
1881 return single_double_animatable_mixer_command(
1883 [](const frame_transform& t) { return t.audio_transform.volume; },
1884 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1887 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1889 sink.short_description(L"Change the volume of an entire channel.");
1890 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1891 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1892 sink.para()->text(L"Examples:");
1893 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1894 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1895 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1898 std::wstring mixer_mastervolume_command(command_context& ctx)
1900 if (ctx.parameters.empty())
1902 auto volume = ctx.channel.channel->mixer().get_master_volume();
1903 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1906 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1907 ctx.channel.channel->mixer().set_master_volume(master_volume);
1909 return L"202 MIXER OK\r\n";
1912 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1914 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1915 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1916 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1917 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1918 sink.para()->text(L"Examples:");
1919 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1920 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1922 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1923 L"<< 201 MIXER OK\n"
1927 std::wstring mixer_straight_alpha_command(command_context& ctx)
1929 if (ctx.parameters.empty())
1931 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1932 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1935 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1936 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1938 return L"202 MIXER OK\r\n";
1941 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1943 sink.short_description(L"Create a grid of video layers.");
1944 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1946 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1947 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1948 sink.para()->text(L"Examples:");
1949 sink.example(L">> MIXER 1 GRID 2");
1952 std::wstring mixer_grid_command(command_context& ctx)
1954 transforms_applier transforms(ctx);
1955 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1956 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1957 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1958 double delta = 1.0 / static_cast<double>(n);
1959 for (int x = 0; x < n; ++x)
1961 for (int y = 0; y < n; ++y)
1963 int index = x + y*n + 1;
1964 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1966 transform.image_transform.fill_translation[0] = x*delta;
1967 transform.image_transform.fill_translation[1] = y*delta;
1968 transform.image_transform.fill_scale[0] = delta;
1969 transform.image_transform.fill_scale[1] = delta;
1970 transform.image_transform.clip_translation[0] = x*delta;
1971 transform.image_transform.clip_translation[1] = y*delta;
1972 transform.image_transform.clip_scale[0] = delta;
1973 transform.image_transform.clip_scale[1] = delta;
1975 }, duration, tween));
1980 return L"202 MIXER OK\r\n";
1983 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1985 sink.short_description(L"Commit all deferred mixer transforms.");
1986 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1987 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1988 sink.para()->text(L"Examples:");
1990 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1991 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1992 L">> MIXER 1 COMMIT");
1995 std::wstring mixer_commit_command(command_context& ctx)
1997 transforms_applier transforms(ctx);
1998 transforms.commit_deferred();
2000 return L"202 MIXER OK\r\n";
2003 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
2005 sink.short_description(L"Clear all transformations on a channel or layer.");
2006 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
2007 sink.para()->text(L"Clears all transformations on a channel or layer.");
2008 sink.para()->text(L"Examples:");
2009 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
2010 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
2013 std::wstring mixer_clear_command(command_context& ctx)
2015 int layer = ctx.layer_id;
2018 ctx.channel.channel->stage().clear_transforms();
2020 ctx.channel.channel->stage().clear_transforms(layer);
2022 return L"202 MIXER OK\r\n";
2025 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2027 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2028 sink.syntax(L"CHANNEL_GRID");
2029 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2031 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2032 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2035 std::wstring channel_grid_command(command_context& ctx)
2038 auto self = ctx.channels.back();
2040 core::diagnostics::scoped_call_context save;
2041 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2043 std::vector<std::wstring> params;
2044 params.push_back(L"SCREEN");
2045 params.push_back(L"0");
2046 params.push_back(L"NAME");
2047 params.push_back(L"Channel Grid Window");
2048 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
2050 self.channel->output().add(screen);
2052 for (auto& channel : ctx.channels)
2054 if (channel.channel != self.channel)
2056 core::diagnostics::call_context::for_thread().layer = index;
2057 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2058 self.channel->stage().load(index, producer, false);
2059 self.channel->stage().play(index);
2064 auto num_channels = ctx.channels.size() - 1;
2065 int square_side_length = std::ceil(std::sqrt(num_channels));
2067 ctx.channel_index = self.channel->index();
2069 ctx.parameters.clear();
2070 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2071 mixer_grid_command(ctx);
2073 return L"202 CHANNEL_GRID OK\r\n";
2076 // Thumbnail Commands
2078 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2080 sink.short_description(L"List all thumbnails.");
2081 sink.syntax(L"THUMBNAIL LIST");
2082 sink.para()->text(L"Lists all thumbnails.");
2083 sink.para()->text(L"Examples:");
2085 L">> THUMBNAIL LIST\n"
2086 L"<< 200 THUMBNAIL LIST OK\n"
2087 L"<< \"AMB\" 20130301T124409 1149\n"
2088 L"<< \"foo/bar\" 20130523T234001 244");
2091 std::wstring thumbnail_list_command(command_context& ctx)
2093 std::wstringstream replyString;
2094 replyString << L"200 THUMBNAIL LIST OK\r\n";
2096 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2098 if (boost::filesystem::is_regular_file(itr->path()))
2100 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2103 auto relativePath = get_relative_without_extension(itr->path(), env::thumbnails_folder());
2104 auto str = relativePath.generic_wstring();
2106 if (str[0] == '\\' || str[0] == '/')
2107 str = std::wstring(str.begin() + 1, str.end());
2109 auto mtime = boost::filesystem::last_write_time(itr->path());
2110 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2111 auto file_size = boost::filesystem::file_size(itr->path());
2113 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2117 replyString << L"\r\n";
2119 return boost::to_upper_copy(replyString.str());
2122 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2124 sink.short_description(L"Retrieve a thumbnail.");
2125 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2126 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2127 sink.para()->text(L"Examples:");
2129 L">> THUMBNAIL RETRIEVE foo/bar\n"
2130 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2131 L"<< ...base64 data...");
2134 std::wstring thumbnail_retrieve_command(command_context& ctx)
2136 std::wstring filename = env::thumbnails_folder();
2137 filename.append(ctx.parameters.at(0));
2138 filename.append(L".png");
2140 std::wstring file_contents;
2142 auto found_file = find_case_insensitive(filename);
2145 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2147 if (file_contents.empty())
2148 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2150 std::wstringstream reply;
2152 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2153 reply << file_contents;
2158 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2160 sink.short_description(L"Regenerate a thumbnail.");
2161 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2162 sink.para()->text(L"Regenerates a thumbnail.");
2165 std::wstring thumbnail_generate_command(command_context& ctx)
2169 ctx.thumb_gen->generate(ctx.parameters.at(0));
2170 return L"202 THUMBNAIL GENERATE OK\r\n";
2173 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2176 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2178 sink.short_description(L"Regenerate all thumbnails.");
2179 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2180 sink.para()->text(L"Regenerates all thumbnails.");
2183 std::wstring thumbnail_generateall_command(command_context& ctx)
2187 ctx.thumb_gen->generate_all();
2188 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2191 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2196 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2198 sink.short_description(L"Get information about a media file.");
2199 sink.syntax(L"CINF [filename:string]");
2200 sink.para()->text(L"Returns information about a media file.");
2203 std::wstring cinf_command(command_context& ctx)
2206 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2208 auto path = itr->path();
2209 auto file = path.replace_extension(L"").filename().wstring();
2210 if (boost::iequals(file, ctx.parameters.at(0)))
2211 info += MediaInfo(itr->path(), ctx.media_info_repo);
2215 CASPAR_THROW_EXCEPTION(file_not_found());
2217 std::wstringstream replyString;
2218 replyString << L"200 CINF OK\r\n";
2219 replyString << info << "\r\n";
2221 return replyString.str();
2224 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2226 sink.short_description(L"List all media files.");
2227 sink.syntax(L"CLS");
2229 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2230 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2233 std::wstring cls_command(command_context& ctx)
2235 std::wstringstream replyString;
2236 replyString << L"200 CLS OK\r\n";
2237 replyString << ListMedia(ctx.media_info_repo);
2238 replyString << L"\r\n";
2239 return boost::to_upper_copy(replyString.str());
2242 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2244 sink.short_description(L"List all fonts.");
2245 sink.syntax(L"FLS");
2247 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2248 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2249 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2252 std::wstring fls_command(command_context& ctx)
2254 std::wstringstream replyString;
2255 replyString << L"200 FLS OK\r\n";
2257 for (auto& font : core::text::list_fonts())
2258 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2260 replyString << L"\r\n";
2262 return replyString.str();
2265 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2267 sink.short_description(L"List all templates.");
2268 sink.syntax(L"TLS");
2270 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2271 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2274 std::wstring tls_command(command_context& ctx)
2276 std::wstringstream replyString;
2277 replyString << L"200 TLS OK\r\n";
2279 replyString << ListTemplates(ctx.cg_registry);
2280 replyString << L"\r\n";
2282 return replyString.str();
2285 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2287 sink.short_description(L"Get version information.");
2288 sink.syntax(L"VERSION {[component:string]}");
2289 sink.para()->text(L"Returns the version of specified component.");
2290 sink.para()->text(L"Examples:");
2293 L"<< 201 VERSION OK\n"
2294 L"<< 2.1.0.f207a33 STABLE");
2296 L">> VERSION SERVER\n"
2297 L"<< 201 VERSION OK\n"
2298 L"<< 2.1.0.f207a33 STABLE");
2300 L">> VERSION FLASH\n"
2301 L"<< 201 VERSION OK\n"
2304 L">> VERSION TEMPLATEHOST\n"
2305 L"<< 201 VERSION OK\n"
2309 L"<< 201 VERSION OK\n"
2313 std::wstring version_command(command_context& ctx)
2315 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2317 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2319 return L"201 VERSION OK\r\n" + version + L"\r\n";
2322 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2325 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2327 sink.short_description(L"Get a list of the available channels.");
2328 sink.syntax(L"INFO");
2329 sink.para()->text(L"Retrieves a list of the available channels.");
2333 L"<< 1 720p5000 PLAYING\n"
2334 L"<< 2 PAL PLAYING");
2337 std::wstring info_command(command_context& ctx)
2339 std::wstringstream replyString;
2340 // This is needed for backwards compatibility with old clients
2341 replyString << L"200 INFO OK\r\n";
2342 for (size_t n = 0; n < ctx.channels.size(); ++n)
2343 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2344 replyString << L"\r\n";
2345 return replyString.str();
2348 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2350 std::wstringstream replyString;
2352 if (command.empty())
2353 replyString << L"201 INFO OK\r\n";
2355 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2357 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2358 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2359 replyString << L"\r\n";
2360 return replyString.str();
2363 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2365 sink.short_description(L"Get information about a channel or a layer.");
2366 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2367 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2368 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2371 std::wstring info_channel_command(command_context& ctx)
2373 boost::property_tree::wptree info;
2374 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2376 if (layer == std::numeric_limits<int>::min())
2378 info.add_child(L"channel", ctx.channel.channel->info())
2379 .add(L"index", ctx.channel_index);
2383 if (ctx.parameters.size() >= 1)
2385 if (boost::iequals(ctx.parameters.at(0), L"B"))
2386 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2388 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2392 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2396 return create_info_xml_reply(info);
2399 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2401 sink.short_description(L"Get information about a template.");
2402 sink.syntax(L"INFO TEMPLATE [template:string]");
2403 sink.para()->text(L"Gets information about the specified template.");
2406 std::wstring info_template_command(command_context& ctx)
2408 auto filename = ctx.parameters.at(0);
2410 std::wstringstream str;
2411 str << u16(ctx.cg_registry->read_meta_info(filename));
2412 boost::property_tree::wptree info;
2413 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2415 return create_info_xml_reply(info, L"TEMPLATE");
2418 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2420 sink.short_description(L"Get the contents of the configuration used.");
2421 sink.syntax(L"INFO CONFIG");
2422 sink.para()->text(L"Gets the contents of the configuration used.");
2425 std::wstring info_config_command(command_context& ctx)
2427 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2430 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2432 sink.short_description(L"Get information about the paths used.");
2433 sink.syntax(L"INFO PATHS");
2434 sink.para()->text(L"Gets information about the paths used.");
2437 std::wstring info_paths_command(command_context& ctx)
2439 boost::property_tree::wptree info;
2440 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2441 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2443 return create_info_xml_reply(info, L"PATHS");
2446 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2448 sink.short_description(L"Get system information.");
2449 sink.syntax(L"INFO SYSTEM");
2450 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2453 std::wstring info_system_command(command_context& ctx)
2455 boost::property_tree::wptree info;
2457 info.add(L"system.name", caspar::system_product_name());
2458 info.add(L"system.os.description", caspar::os_description());
2459 info.add(L"system.cpu", caspar::cpu_info());
2461 ctx.system_info_repo->fill_information(info);
2463 return create_info_xml_reply(info, L"SYSTEM");
2466 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2468 sink.short_description(L"Get detailed information about all channels.");
2469 sink.syntax(L"INFO SERVER");
2470 sink.para()->text(L"Gets detailed information about all channels.");
2473 std::wstring info_server_command(command_context& ctx)
2475 boost::property_tree::wptree info;
2478 for (auto& channel : ctx.channels)
2479 info.add_child(L"channels.channel", channel.channel->info())
2480 .add(L"index", ++index);
2482 return create_info_xml_reply(info, L"SERVER");
2485 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2487 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2488 sink.syntax(L"INFO QUEUES");
2489 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2492 std::wstring info_queues_command(command_context& ctx)
2494 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2497 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2499 sink.short_description(L"Lists all known threads in the server.");
2500 sink.syntax(L"INFO THREADS");
2501 sink.para()->text(L"Lists all known threads in the server.");
2504 std::wstring info_threads_command(command_context& ctx)
2506 std::wstringstream replyString;
2507 replyString << L"200 INFO THREADS OK\r\n";
2509 for (auto& thread : get_thread_infos())
2511 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2514 replyString << L"\r\n";
2515 return replyString.str();
2518 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2520 sink.short_description(L"Get the current delay on a channel or a layer.");
2521 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2522 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2525 std::wstring info_delay_command(command_context& ctx)
2527 boost::property_tree::wptree info;
2528 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2530 if (layer == std::numeric_limits<int>::min())
2531 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2533 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2534 .add(L"index", layer);
2536 return create_info_xml_reply(info, L"DELAY");
2539 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2541 sink.short_description(L"Open the diagnostics window.");
2542 sink.syntax(L"DIAG");
2543 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2546 std::wstring diag_command(command_context& ctx)
2548 core::diagnostics::osd::show_graphs(true);
2550 return L"202 DIAG OK\r\n";
2553 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2555 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2556 sink.syntax(L"GL INFO");
2557 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2560 std::wstring gl_info_command(command_context& ctx)
2562 auto device = ctx.ogl_device;
2565 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2567 std::wstringstream result;
2568 result << L"201 GL INFO OK\r\n";
2570 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2571 auto info = device->info();
2572 boost::property_tree::write_xml(result, info, w);
2575 return result.str();
2578 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2580 sink.short_description(L"Release pooled OpenGL resources.");
2581 sink.syntax(L"GL GC");
2582 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2585 std::wstring gl_gc_command(command_context& ctx)
2587 auto device = ctx.ogl_device;
2590 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2592 device->gc().wait();
2594 return L"202 GL GC OK\r\n";
2597 static const int WIDTH = 80;
2599 struct max_width_sink : public core::help_sink
2601 std::size_t max_width = 0;
2603 void begin_item(const std::wstring& name) override
2605 max_width = std::max(name.length(), max_width);
2609 struct short_description_sink : public core::help_sink
2612 std::wstringstream& out;
2614 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2616 void begin_item(const std::wstring& name) override
2618 out << std::left << std::setw(width + 1) << name;
2621 void short_description(const std::wstring& short_description) override
2623 out << short_description << L"\r\n";
2627 struct simple_paragraph_builder : core::paragraph_builder
2629 std::wostringstream out;
2630 std::wstringstream& commit_to;
2632 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2633 ~simple_paragraph_builder()
2635 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2637 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2639 out << std::move(text);
2640 return shared_from_this();
2642 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2643 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2644 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2645 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2648 struct simple_definition_list_builder : core::definition_list_builder
2650 std::wstringstream& out;
2652 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2653 ~simple_definition_list_builder()
2658 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2660 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2661 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2662 return shared_from_this();
2666 struct long_description_sink : public core::help_sink
2668 std::wstringstream& out;
2670 long_description_sink(std::wstringstream& out) : out(out) { }
2672 void syntax(const std::wstring& syntax) override
2674 out << L"Syntax:\n";
2675 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2678 spl::shared_ptr<core::paragraph_builder> para() override
2680 return spl::make_shared<simple_paragraph_builder>(out);
2683 spl::shared_ptr<core::definition_list_builder> definitions() override
2685 return spl::make_shared<simple_definition_list_builder>(out);
2688 void example(const std::wstring& code, const std::wstring& caption) override
2690 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2692 if (!caption.empty())
2693 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2698 void begin_item(const std::wstring& name) override
2700 out << name << L"\n\n";
2704 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2706 std::wstringstream result;
2707 result << L"200 " << help_command << L" OK\r\n";
2708 max_width_sink width;
2709 ctx.help_repo->help(tags, width);
2710 short_description_sink sink(width.max_width, result);
2711 sink.width = width.max_width;
2712 ctx.help_repo->help(tags, sink);
2714 return result.str();
2717 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2719 std::wstringstream result;
2720 result << L"201 " << help_command << L" OK\r\n";
2721 auto joined = boost::join(ctx.parameters, L" ");
2722 long_description_sink sink(result);
2723 ctx.help_repo->help(tags, joined, sink);
2725 return result.str();
2728 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2730 sink.short_description(L"Show online help for AMCP commands.");
2731 sink.syntax(LR"(HELP {[command:string]})");
2732 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2733 sink.example(L">> HELP", L"Shows a list of commands.");
2734 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2737 std::wstring help_command(command_context& ctx)
2739 if (ctx.parameters.size() == 0)
2740 return create_help_list(L"HELP", ctx, { L"AMCP" });
2742 return create_help_details(L"HELP", ctx, { L"AMCP" });
2745 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2747 sink.short_description(L"Show online help for producers.");
2748 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2749 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2750 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2751 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2754 std::wstring help_producer_command(command_context& ctx)
2756 if (ctx.parameters.size() == 0)
2757 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2759 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2762 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2764 sink.short_description(L"Show online help for consumers.");
2765 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2766 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2767 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2768 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2771 std::wstring help_consumer_command(command_context& ctx)
2773 if (ctx.parameters.size() == 0)
2774 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2776 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2779 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2781 sink.short_description(L"Disconnect the session.");
2782 sink.syntax(L"BYE");
2784 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2785 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2788 std::wstring bye_command(command_context& ctx)
2790 ctx.client->disconnect();
2794 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2796 sink.short_description(L"Shutdown the server.");
2797 sink.syntax(L"KILL");
2798 sink.para()->text(L"Shuts the server down.");
2801 std::wstring kill_command(command_context& ctx)
2803 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2804 return L"202 KILL OK\r\n";
2807 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2809 sink.short_description(L"Shutdown the server with restart exit code.");
2810 sink.syntax(L"RESTART");
2812 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2813 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2816 std::wstring restart_command(command_context& ctx)
2818 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2819 return L"202 RESTART OK\r\n";
2822 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2824 sink.short_description(L"Lock or unlock access to a channel.");
2825 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2826 sink.para()->text(L"Allows for exclusive access to a channel.");
2827 sink.para()->text(L"Examples:");
2828 sink.example(L"LOCK 1 ACQUIRE secret");
2829 sink.example(L"LOCK 1 RELEASE");
2830 sink.example(L"LOCK 1 CLEAR");
2833 std::wstring lock_command(command_context& ctx)
2835 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2836 auto lock = ctx.channels.at(channel_index).lock;
2837 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2839 if (command == L"ACQUIRE")
2841 std::wstring lock_phrase = ctx.parameters.at(2);
2843 //TODO: read options
2845 //just lock one channel
2846 if (!lock->try_lock(lock_phrase, ctx.client))
2847 return L"503 LOCK ACQUIRE FAILED\r\n";
2849 return L"202 LOCK ACQUIRE OK\r\n";
2851 else if (command == L"RELEASE")
2853 lock->release_lock(ctx.client);
2854 return L"202 LOCK RELEASE OK\r\n";
2856 else if (command == L"CLEAR")
2858 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2859 std::wstring client_override_phrase;
2861 if (!override_phrase.empty())
2862 client_override_phrase = ctx.parameters.at(2);
2864 //just clear one channel
2865 if (client_override_phrase != override_phrase)
2866 return L"503 LOCK CLEAR FAILED\r\n";
2868 lock->clear_locks();
2870 return L"202 LOCK CLEAR OK\r\n";
2873 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2876 void register_commands(amcp_command_repository& repo)
2878 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2879 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2880 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2881 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2882 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2883 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2884 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2885 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2886 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2887 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2888 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2889 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2890 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2891 repo.register_command( L"Basic Commands", L"LOG CATEGORY", log_category_describer, log_category_command, 2);
2892 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2893 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2895 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2896 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2897 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2898 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2900 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2901 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2902 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2903 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2904 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2905 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2906 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2907 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2908 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2910 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2911 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2912 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2913 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2914 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2915 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2916 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2917 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2918 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2919 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2920 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2921 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2922 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2923 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2924 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2925 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2926 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2927 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2928 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2929 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2930 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2931 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2933 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2934 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2935 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2936 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2938 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2939 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2940 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
2941 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2942 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2943 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2944 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2945 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2946 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2947 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2948 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2949 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2950 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2951 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2952 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2953 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2954 repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
2955 repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
2956 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2957 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2958 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2959 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2960 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2961 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2965 }} //namespace caspar