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 std::vector<spl::shared_ptr<core::video_channel>> get_channels(const command_context& ctx)
285 return cpplinq::from(ctx.channels)
286 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
290 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
292 return core::frame_producer_dependencies(
293 channel->frame_factory(),
295 channel->video_format_desc(),
296 ctx.producer_registry);
301 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
303 sink.short_description(L"Load a media file or resource in the background.");
304 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]})");
306 ->text(L"Loads a producer in the background and prepares it for playout. ")
307 ->text(L"If no layer is specified the default layer index will be used.");
309 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
310 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
312 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
313 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
315 ->code(L"loop")->text(L" will cause the clip to loop.");
317 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
319 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
321 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
322 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
323 sink.para()->text(L"Examples:");
324 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
325 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
326 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
327 sink.example(L">> LOADBG 1-0 MY_FILE");
329 L">> PLAY 1-1 MY_FILE\n"
330 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
331 L"To automatically fade out a layer after a video file has been played to the end");
333 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
335 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
336 ->code(L"filter")->text(L" command.");
339 std::wstring loadbg_command(command_context& ctx)
341 transition_info transitionInfo;
345 std::wstring message;
346 for (size_t n = 0; n < ctx.parameters.size(); ++n)
347 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
349 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)?.*)");
351 if (boost::regex_match(message, what, expr))
353 auto transition = what["TRANSITION"].str();
354 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
355 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
356 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
357 transitionInfo.tweener = tween;
359 if (transition == L"CUT")
360 transitionInfo.type = transition_type::cut;
361 else if (transition == L"MIX")
362 transitionInfo.type = transition_type::mix;
363 else if (transition == L"PUSH")
364 transitionInfo.type = transition_type::push;
365 else if (transition == L"SLIDE")
366 transitionInfo.type = transition_type::slide;
367 else if (transition == L"WIPE")
368 transitionInfo.type = transition_type::wipe;
370 if (direction == L"FROMLEFT")
371 transitionInfo.direction = transition_direction::from_left;
372 else if (direction == L"FROMRIGHT")
373 transitionInfo.direction = transition_direction::from_right;
374 else if (direction == L"LEFT")
375 transitionInfo.direction = transition_direction::from_right;
376 else if (direction == L"RIGHT")
377 transitionInfo.direction = transition_direction::from_left;
380 //Perform loading of the clip
381 core::diagnostics::scoped_call_context save;
382 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
383 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
385 auto channel = ctx.channel.channel;
386 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
388 if (pFP == frame_producer::empty())
389 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
391 bool auto_play = contains_param(L"AUTO", ctx.parameters);
393 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
395 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
397 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
399 return L"202 LOADBG OK\r\n";
402 void load_describer(core::help_sink& sink, const core::help_repository& repo)
404 sink.short_description(L"Load a media file or resource to the foreground.");
405 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
407 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
408 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
409 sink.para()->text(L"Examples:");
410 sink.example(L">> LOAD 1 MY_FILE");
411 sink.example(L">> LOAD 1-1 MY_FILE");
412 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
415 std::wstring load_command(command_context& ctx)
417 core::diagnostics::scoped_call_context save;
418 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
419 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
420 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
421 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
423 return L"202 LOAD OK\r\n";
426 void play_describer(core::help_sink& sink, const core::help_repository& repository)
428 sink.short_description(L"Play a media file or resource.");
429 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
431 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
432 ->text(L") is prepared, it will be executed.");
434 ->text(L"If additional parameters (see ")->see(L"LOADBG")
435 ->text(L") are provided then the provided clip will first be loaded to the background.");
436 sink.para()->text(L"Examples:");
437 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
438 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
439 sink.example(L">> PLAY 1-0 MY_FILE");
440 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
443 std::wstring play_command(command_context& ctx)
445 if (!ctx.parameters.empty())
448 ctx.channel.channel->stage().play(ctx.layer_index());
450 return L"202 PLAY OK\r\n";
453 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
455 sink.short_description(L"Pause playback of a layer.");
456 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
458 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
459 ->text(L" command can be used to resume playback again.");
460 sink.para()->text(L"Examples:");
461 sink.example(L">> PAUSE 1");
462 sink.example(L">> PAUSE 1-1");
465 std::wstring pause_command(command_context& ctx)
467 ctx.channel.channel->stage().pause(ctx.layer_index());
468 return L"202 PAUSE OK\r\n";
471 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
473 sink.short_description(L"Resume playback of a layer.");
474 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
476 ->text(L"Resumes playback of a foreground clip previously paused with the ")
477 ->see(L"PAUSE")->text(L" command.");
478 sink.para()->text(L"Examples:");
479 sink.example(L">> RESUME 1");
480 sink.example(L">> RESUME 1-1");
483 std::wstring resume_command(command_context& ctx)
485 ctx.channel.channel->stage().resume(ctx.layer_index());
486 return L"202 RESUME OK\r\n";
489 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
491 sink.short_description(L"Remove the foreground clip of a layer.");
492 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
494 ->text(L"Removes the foreground clip of the specified layer.");
495 sink.para()->text(L"Examples:");
496 sink.example(L">> STOP 1");
497 sink.example(L">> STOP 1-1");
500 std::wstring stop_command(command_context& ctx)
502 ctx.channel.channel->stage().stop(ctx.layer_index());
503 return L"202 STOP OK\r\n";
506 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
508 sink.short_description(L"Remove all clips of a layer or an entire channel.");
509 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
511 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
512 ->text(L"If no layer is specified then all layers in the specified ")
513 ->code(L"video_channel")->text(L" are cleared.");
514 sink.para()->text(L"Examples:");
515 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
516 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
519 std::wstring clear_command(command_context& ctx)
521 int index = ctx.layer_index(std::numeric_limits<int>::min());
522 if (index != std::numeric_limits<int>::min())
523 ctx.channel.channel->stage().clear(index);
525 ctx.channel.channel->stage().clear();
527 return L"202 CLEAR OK\r\n";
530 void call_describer(core::help_sink& sink, const core::help_repository& repo)
532 sink.short_description(L"Call a method on a producer.");
533 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
535 ->text(L"Calls method on the specified producer with the provided ")
536 ->code(L"param")->text(L" string.");
537 sink.para()->text(L"Examples:");
538 sink.example(L">> CALL 1 LOOP");
539 sink.example(L">> CALL 1-2 SEEK 25");
542 std::wstring call_command(command_context& ctx)
544 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters).get();
546 // TODO: because of std::async deferred timed waiting does not work
548 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
549 if (wait_res == std::future_status::timeout)
550 CASPAR_THROW_EXCEPTION(timed_out());*/
552 std::wstringstream replyString;
554 replyString << L"202 CALL OK\r\n";
556 replyString << L"201 CALL OK\r\n" << result << L"\r\n";
558 return replyString.str();
561 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
563 sink.short_description(L"Swap layers between channels.");
564 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
566 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
567 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
568 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
569 sink.para()->text(L"Examples:");
570 sink.example(L">> SWAP 1 2");
571 sink.example(L">> SWAP 1-1 2-3");
572 sink.example(L">> SWAP 1-1 1-2");
573 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
576 std::wstring swap_command(command_context& ctx)
578 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
580 if (ctx.layer_index(-1) != -1)
582 std::vector<std::string> strs;
583 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
585 auto ch1 = ctx.channel.channel;
586 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
588 int l1 = ctx.layer_index();
589 int l2 = boost::lexical_cast<int>(strs.at(1));
591 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
595 auto ch1 = ctx.channel.channel;
596 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
597 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
600 return L"202 SWAP OK\r\n";
603 void add_describer(core::help_sink& sink, const core::help_repository& repo)
605 sink.short_description(L"Add a consumer to a video channel.");
606 sink.syntax(L"ADD [video_channel:int]{-[consumer_index:int]} [consumer:string] [parameters:string]");
608 ->text(L"Adds a consumer to the specified video channel. The string ")
609 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
610 ->text(L"If a successful match is found a consumer will be created and added to the ")
611 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
612 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
613 ->see(L"the CasparCG config file")->text(L".");
615 ->text(L"Specifying ")->code(L"consumer_index")
616 ->text(L" overrides the index that the consumer itself decides and can later be used with the ")
617 ->see(L"REMOVE")->text(L" command to remove the consumer.");
618 sink.para()->text(L"Examples:");
619 sink.example(L">> ADD 1 DECKLINK 1");
620 sink.example(L">> ADD 1 BLUEFISH 2");
621 sink.example(L">> ADD 1 SCREEN");
622 sink.example(L">> ADD 1 AUDIO");
623 sink.example(L">> ADD 1 IMAGE filename");
624 sink.example(L">> ADD 2 SYNCTO 1");
625 sink.example(L">> ADD 1 FILE filename.mov");
626 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
628 L">> ADD 1-700 FILE filename.mov SEPARATE_KEY\n"
629 L">> REMOVE 1-700", L"overriding the consumer index to easier remove later.");
630 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
631 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
634 std::wstring add_command(command_context& ctx)
636 replace_placeholders(
637 L"<CLIENT_IP_ADDRESS>",
638 ctx.client->address(),
641 core::diagnostics::scoped_call_context save;
642 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
644 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx));
645 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
647 return L"202 ADD OK\r\n";
650 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
652 sink.short_description(L"Remove a consumer from a video channel.");
653 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
655 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
656 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
657 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
658 sink.para()->text(L"Examples:");
659 sink.example(L">> REMOVE 1 DECKLINK 1");
660 sink.example(L">> REMOVE 1 BLUEFISH 2");
661 sink.example(L">> REMOVE 1 SCREEN");
662 sink.example(L">> REMOVE 1 AUDIO");
663 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
666 std::wstring remove_command(command_context& ctx)
668 auto index = ctx.layer_index(std::numeric_limits<int>::min());
670 if (index == std::numeric_limits<int>::min())
672 replace_placeholders(
673 L"<CLIENT_IP_ADDRESS>",
674 ctx.client->address(),
677 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx))->index();
680 ctx.channel.channel->output().remove(index);
682 return L"202 REMOVE OK\r\n";
685 void print_describer(core::help_sink& sink, const core::help_repository& repo)
687 sink.short_description(L"Take a snapshot of a channel.");
688 sink.syntax(L"PRINT [video_channel:int]");
690 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
691 ->code(L"media")->text(L" folder.");
692 sink.para()->text(L"Examples:");
693 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
696 std::wstring print_command(command_context& ctx)
698 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage(), get_channels(ctx)));
700 return L"202 PRINT OK\r\n";
703 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
705 sink.short_description(L"Change the log level of the server.");
706 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
707 sink.para()->text(L"Changes the log level of the server.");
708 sink.para()->text(L"Examples:");
709 sink.example(L">> LOG LEVEL trace");
710 sink.example(L">> LOG LEVEL info");
713 std::wstring log_level_command(command_context& ctx)
715 log::set_log_level(ctx.parameters.at(0));
717 return L"202 LOG OK\r\n";
720 void log_category_describer(core::help_sink& sink, const core::help_repository& repo)
722 sink.short_description(L"Enable/disable a logging category in the server.");
723 sink.syntax(L"LOG CATEGORY [category:calltrace,communication] [enable:0,1]");
724 sink.para()->text(L"Enables or disables the specified logging category.");
725 sink.para()->text(L"Examples:");
726 sink.example(L">> LOG CATEGORY calltrace 1", L"to enable call trace");
727 sink.example(L">> LOG CATEGORY calltrace 0", L"to disable call trace");
730 std::wstring log_category_command(command_context& ctx)
732 log::set_log_category(ctx.parameters.at(0), ctx.parameters.at(1) == L"1");
734 return L"202 LOG OK\r\n";
737 void set_describer(core::help_sink& sink, const core::help_repository& repo)
739 sink.short_description(L"Change the value of a channel variable.");
740 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
741 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
743 ->item(L"MODE", L"Changes the video format of the channel.")
744 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
745 sink.para()->text(L"Examples:");
746 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
747 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
750 std::wstring set_command(command_context& ctx)
752 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
753 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
757 auto format_desc = core::video_format_desc(value);
758 if (format_desc.format != core::video_format::invalid)
760 ctx.channel.channel->video_format_desc(format_desc);
761 return L"202 SET MODE OK\r\n";
764 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video mode"));
766 else if (name == L"CHANNEL_LAYOUT")
768 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
772 ctx.channel.channel->audio_channel_layout(*channel_layout);
773 return L"202 SET CHANNEL_LAYOUT OK\r\n";
776 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid audio channel layout"));
779 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid channel variable"));
782 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
784 sink.short_description(L"Store a dataset.");
785 sink.syntax(L"DATA STORE [name:string] [data:string]");
786 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
787 sink.para()->text(L"Directories will be created if they do not exist.");
788 sink.para()->text(L"Examples:");
789 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
790 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
793 std::wstring data_store_command(command_context& ctx)
795 std::wstring filename = env::data_folder();
796 filename.append(ctx.parameters[0]);
797 filename.append(L".ftd");
799 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
800 auto found_data_path = find_case_insensitive(data_path);
803 data_path = *found_data_path;
805 if (!boost::filesystem::exists(data_path))
806 boost::filesystem::create_directories(data_path);
808 auto found_filename = find_case_insensitive(filename);
811 filename = *found_filename; // Overwrite case insensitive.
813 boost::filesystem::wofstream datafile(filename);
815 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
817 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
818 datafile << ctx.parameters[1] << std::flush;
821 return L"202 DATA STORE OK\r\n";
824 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
826 sink.short_description(L"Retrieve a dataset.");
827 sink.syntax(L"DATA RETRIEVE [name:string]");
828 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
829 sink.para()->text(L"Examples:");
830 sink.example(L">> DATA RETRIEVE my_data");
831 sink.example(L">> DATA RETRIEVE Folder1/my_data");
834 std::wstring data_retrieve_command(command_context& ctx)
836 std::wstring filename = env::data_folder();
837 filename.append(ctx.parameters[0]);
838 filename.append(L".ftd");
840 std::wstring file_contents;
842 auto found_file = find_case_insensitive(filename);
845 file_contents = read_file(boost::filesystem::path(*found_file));
847 if (file_contents.empty())
848 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
850 std::wstringstream reply;
851 reply << L"201 DATA RETRIEVE OK\r\n";
853 std::wstringstream file_contents_stream(file_contents);
856 bool firstLine = true;
857 while (std::getline(file_contents_stream, line))
871 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
873 sink.short_description(L"List stored datasets.");
874 sink.syntax(L"DATA LIST");
875 sink.para()->text(L"Returns a list of all stored datasets.");
878 std::wstring data_list_command(command_context& ctx)
880 std::wstringstream replyString;
881 replyString << L"200 DATA LIST OK\r\n";
883 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
885 if (boost::filesystem::is_regular_file(itr->path()))
887 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
890 auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
891 auto str = relativePath.generic_wstring();
893 if (str[0] == L'\\' || str[0] == L'/')
894 str = std::wstring(str.begin() + 1, str.end());
896 replyString << str << L"\r\n";
900 replyString << L"\r\n";
902 return boost::to_upper_copy(replyString.str());
905 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
907 sink.short_description(L"Remove a stored dataset.");
908 sink.syntax(L"DATA REMOVE [name:string]");
909 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
910 sink.para()->text(L"Examples:");
911 sink.example(L">> DATA REMOVE my_data");
912 sink.example(L">> DATA REMOVE Folder1/my_data");
915 std::wstring data_remove_command(command_context& ctx)
917 std::wstring filename = env::data_folder();
918 filename.append(ctx.parameters[0]);
919 filename.append(L".ftd");
921 if (!boost::filesystem::exists(filename))
922 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
924 if (!boost::filesystem::remove(filename))
925 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
927 return L"202 DATA REMOVE OK\r\n";
930 // Template Graphics Commands
932 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
934 sink.short_description(L"Prepare a template for displaying.");
935 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
937 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
938 ->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.");
939 sink.para()->text(L"Examples:");
940 sink.example(L"CG 1 ADD 10 svtnews/info 1");
943 std::wstring cg_add_command(command_context& ctx)
945 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
947 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
948 std::wstring label; //_parameters[2]
949 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
950 unsigned int dataIndex = 3;
952 if (ctx.parameters.at(2).length() > 1)
954 label = ctx.parameters.at(2);
957 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
958 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
961 { //read play-on-load-flag
962 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
965 const wchar_t* pDataString = 0;
966 std::wstring dataFromFile;
967 if (ctx.parameters.size() > dataIndex)
969 const std::wstring& dataString = ctx.parameters.at(dataIndex);
971 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
972 pDataString = dataString.c_str();
975 //The data is not an XML-string, it must be a filename
976 std::wstring filename = env::data_folder();
977 filename.append(dataString);
978 filename.append(L".ftd");
980 auto found_file = find_case_insensitive(filename);
984 dataFromFile = read_file(boost::filesystem::path(*found_file));
985 pDataString = dataFromFile.c_str();
990 auto filename = ctx.parameters.at(1);
991 auto proxy = ctx.cg_registry->get_or_create_proxy(
992 spl::make_shared_ptr(ctx.channel.channel),
993 get_producer_dependencies(ctx.channel.channel, ctx),
994 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
997 if (proxy == core::cg_proxy::empty())
998 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
1000 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
1002 return L"202 CG OK\r\n";
1005 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
1007 sink.short_description(L"Play and display a template.");
1008 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
1009 sink.para()->text(L"Plays and displays the template in the specified layer.");
1010 sink.para()->text(L"Examples:");
1011 sink.example(L"CG 1 PLAY 0");
1014 std::wstring cg_play_command(command_context& ctx)
1016 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1017 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1019 return L"202 CG OK\r\n";
1022 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1024 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1026 if (proxy == cg_proxy::empty())
1027 CASPAR_THROW_EXCEPTION(expected_user_error() << msg_info(L"No CG proxy running on layer"));
1032 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1034 sink.short_description(L"Stop and remove a template.");
1035 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1037 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1038 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1039 sink.para()->text(L"Examples:");
1040 sink.example(L"CG 1 STOP 0");
1043 std::wstring cg_stop_command(command_context& ctx)
1045 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1046 get_expected_cg_proxy(ctx)->stop(layer, 0);
1048 return L"202 CG OK\r\n";
1051 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1053 sink.short_description(LR"(Trigger a "continue" in a template.)");
1054 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1056 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1057 ->text(L"This is used to control animations that has multiple discreet steps.");
1058 sink.para()->text(L"Examples:");
1059 sink.example(L"CG 1 NEXT 0");
1062 std::wstring cg_next_command(command_context& ctx)
1064 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1065 get_expected_cg_proxy(ctx)->next(layer);
1067 return L"202 CG OK\r\n";
1070 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1072 sink.short_description(L"Remove a template.");
1073 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1074 sink.para()->text(L"Removes the template from the specified layer.");
1075 sink.para()->text(L"Examples:");
1076 sink.example(L"CG 1 REMOVE 0");
1079 std::wstring cg_remove_command(command_context& ctx)
1081 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1082 get_expected_cg_proxy(ctx)->remove(layer);
1084 return L"202 CG OK\r\n";
1087 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1089 sink.short_description(L"Remove all templates on a video layer.");
1090 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1091 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1092 sink.para()->text(L"Examples:");
1093 sink.example(L"CG 1 CLEAR");
1096 std::wstring cg_clear_command(command_context& ctx)
1098 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1100 return L"202 CG OK\r\n";
1103 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1105 sink.short_description(L"Update a template with new data.");
1106 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1107 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.");
1110 std::wstring cg_update_command(command_context& ctx)
1112 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1114 std::wstring dataString = ctx.parameters.at(1);
1115 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1117 //The data is not XML or Json, it must be a filename
1118 std::wstring filename = env::data_folder();
1119 filename.append(dataString);
1120 filename.append(L".ftd");
1122 dataString = read_file(boost::filesystem::path(filename));
1125 get_expected_cg_proxy(ctx)->update(layer, dataString);
1127 return L"202 CG OK\r\n";
1130 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1132 sink.short_description(L"Invoke a method/label on a template.");
1133 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1134 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1135 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1138 std::wstring cg_invoke_command(command_context& ctx)
1140 std::wstringstream replyString;
1141 replyString << L"201 CG OK\r\n";
1142 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1143 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1144 replyString << result << L"\r\n";
1146 return replyString.str();
1149 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1151 sink.short_description(L"Get information about a running template or the template host.");
1152 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1153 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1154 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1157 std::wstring cg_info_command(command_context& ctx)
1159 std::wstringstream replyString;
1160 replyString << L"201 CG OK\r\n";
1162 if (ctx.parameters.empty())
1164 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1165 replyString << info << L"\r\n";
1169 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1170 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1172 replyString << desc << L"\r\n";
1175 return replyString.str();
1180 core::frame_transform get_current_transform(command_context& ctx)
1182 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1185 template<typename Func>
1186 std::wstring reply_value(command_context& ctx, const Func& extractor)
1188 auto value = extractor(get_current_transform(ctx));
1190 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1193 class transforms_applier
1195 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1197 std::vector<stage::transform_tuple_t> transforms_;
1198 command_context& ctx_;
1201 transforms_applier(command_context& ctx)
1204 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1207 ctx.parameters.pop_back();
1210 void add(stage::transform_tuple_t&& transform)
1212 transforms_.push_back(std::move(transform));
1215 void commit_deferred()
1217 auto& transforms = deferred_transforms_[ctx_.channel_index];
1218 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1226 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1227 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1230 ctx_.channel.channel->stage().apply_transforms(transforms_);
1233 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1235 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1237 sink.short_description(L"Let a layer act as alpha for the one obove.");
1238 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1240 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1241 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1242 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1243 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1244 ->text(L"instead it will be used as the key for the layer above.");
1245 sink.para()->text(L"Examples:");
1246 sink.example(L">> MIXER 1-0 KEYER 1");
1248 L">> MIXER 1-0 KEYER\n"
1249 L"<< 201 MIXER OK\n"
1250 L"<< 1", L"to retrieve the current state");
1253 std::wstring mixer_keyer_command(command_context& ctx)
1255 if (ctx.parameters.empty())
1256 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1258 transforms_applier transforms(ctx);
1259 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1260 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1262 transform.image_transform.is_key = value;
1264 }, 0, tweener(L"linear")));
1267 return L"202 MIXER OK\r\n";
1270 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1272 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1274 sink.short_description(L"Enable chroma keying on a layer.");
1275 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1277 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1278 sink.para()->text(L"Examples:");
1279 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1280 sink.example(L">> MIXER 1-1 CHROMA none");
1282 L">> MIXER 1-1 BLEND\n"
1283 L"<< 201 MIXER OK\n"
1284 L"<< SCREEN", L"for getting the current blend mode");
1287 std::wstring mixer_chroma_command(command_context& ctx)
1289 if (ctx.parameters.empty())
1291 auto chroma = get_current_transform(ctx).image_transform.chroma;
1292 return L"201 MIXER OK\r\n"
1293 + core::get_chroma_mode(chroma.key) + L" "
1294 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1295 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1296 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1299 transforms_applier transforms(ctx);
1300 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1301 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1303 core::chroma chroma;
1304 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1306 if (chroma.key != core::chroma::type::none)
1308 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1309 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1310 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1313 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1315 transform.image_transform.chroma = chroma;
1317 }, duration, tween));
1320 return L"202 MIXER OK\r\n";
1323 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1325 sink.short_description(L"Set the blend mode for a layer.");
1326 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1328 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1329 ->text(L"If no argument is given the current blend mode is returned.");
1331 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1332 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1333 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1334 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1335 sink.para()->text(L"Examples:");
1336 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1338 L">> MIXER 1-1 BLEND\n"
1339 L"<< 201 MIXER OK\n"
1340 L"<< SCREEN", L"for getting the current blend mode");
1341 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1344 std::wstring mixer_blend_command(command_context& ctx)
1346 if (ctx.parameters.empty())
1347 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1349 transforms_applier transforms(ctx);
1350 auto value = get_blend_mode(ctx.parameters.at(0));
1351 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1353 transform.image_transform.blend_mode = value;
1355 }, 0, tweener(L"linear")));
1358 return L"202 MIXER OK\r\n";
1361 template<typename Getter, typename Setter>
1362 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1364 if (ctx.parameters.empty())
1365 return reply_value(ctx, getter);
1367 transforms_applier transforms(ctx);
1368 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1369 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1370 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1372 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1374 setter(transform, value);
1376 }, duration, tween));
1379 return L"202 MIXER OK\r\n";
1382 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1384 sink.short_description(L"Change the opacity of a layer.");
1385 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1386 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1387 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1388 sink.para()->text(L"Examples:");
1389 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1391 L">> MIXER 1-0 OPACITY\n"
1392 L"<< 201 MIXER OK\n"
1393 L"<< 0.5", L"to retrieve the current opacity");
1396 std::wstring mixer_opacity_command(command_context& ctx)
1398 return single_double_animatable_mixer_command(
1400 [](const frame_transform& t) { return t.image_transform.opacity; },
1401 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1404 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1406 sink.short_description(L"Change the brightness of a layer.");
1407 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1408 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1409 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1410 sink.para()->text(L"Examples:");
1411 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1413 L">> MIXER 1-0 BRIGHTNESS\n"
1414 L"<< 201 MIXER OK\n"
1415 L"0.5", L"to retrieve the current brightness");
1418 std::wstring mixer_brightness_command(command_context& ctx)
1420 return single_double_animatable_mixer_command(
1422 [](const frame_transform& t) { return t.image_transform.brightness; },
1423 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1426 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1428 sink.short_description(L"Change the saturation of a layer.");
1429 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1430 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1431 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1432 sink.para()->text(L"Examples:");
1433 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1435 L">> MIXER 1-0 SATURATION\n"
1436 L"<< 201 MIXER OK\n"
1437 L"<< 0.5", L"to retrieve the current saturation");
1440 std::wstring mixer_saturation_command(command_context& ctx)
1442 return single_double_animatable_mixer_command(
1444 [](const frame_transform& t) { return t.image_transform.saturation; },
1445 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1448 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1450 sink.short_description(L"Change the contrast of a layer.");
1451 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1452 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1453 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1454 sink.para()->text(L"Examples:");
1455 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1457 L">> MIXER 1-0 CONTRAST\n"
1458 L"<< 201 MIXER OK\n"
1459 L"<< 0.5", L"to retrieve the current contrast");
1462 std::wstring mixer_contrast_command(command_context& ctx)
1464 return single_double_animatable_mixer_command(
1466 [](const frame_transform& t) { return t.image_transform.contrast; },
1467 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1470 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1472 sink.short_description(L"Adjust the video levels of a layer.");
1473 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);
1475 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1477 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1478 ->item(L"gamma", L"Adjusts the gamma of the image.")
1479 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1480 sink.para()->text(L"Examples:");
1481 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");
1482 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");
1483 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1485 L">> MIXER 1-10 LEVELS\n"
1486 L"<< 201 MIXER OK\n"
1487 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1490 std::wstring mixer_levels_command(command_context& ctx)
1492 if (ctx.parameters.empty())
1494 auto levels = get_current_transform(ctx).image_transform.levels;
1495 return L"201 MIXER OK\r\n"
1496 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1497 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1498 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1499 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1500 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1503 transforms_applier transforms(ctx);
1505 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1506 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1507 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1508 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1509 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1510 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1511 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1513 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1515 transform.image_transform.levels = value;
1517 }, duration, tween));
1520 return L"202 MIXER OK\r\n";
1523 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1525 sink.short_description(L"Change the fill position and scale of a layer.");
1526 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1528 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1529 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1530 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1531 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1533 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1534 ->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.");
1535 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1537 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1538 ->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, ")
1539 ->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");
1541 ->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.")
1542 ->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.")
1543 ->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.")
1544 ->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.");
1545 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1546 sink.para()->text(L"Examples:");
1547 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1549 L">> MIXER 1-0 FILL\n"
1550 L"<< 201 MIXER OK\n"
1551 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1554 std::wstring mixer_fill_command(command_context& ctx)
1556 if (ctx.parameters.empty())
1558 auto transform = get_current_transform(ctx).image_transform;
1559 auto translation = transform.fill_translation;
1560 auto scale = transform.fill_scale;
1561 return L"201 MIXER OK\r\n"
1562 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1563 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1564 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1565 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1568 transforms_applier transforms(ctx);
1569 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1570 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1571 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1572 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1573 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1574 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1576 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1578 transform.image_transform.fill_translation[0] = x;
1579 transform.image_transform.fill_translation[1] = y;
1580 transform.image_transform.fill_scale[0] = x_s;
1581 transform.image_transform.fill_scale[1] = y_s;
1583 }, duration, tween));
1586 return L"202 MIXER OK\r\n";
1589 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1591 sink.short_description(L"Change the clipping viewport of a layer.");
1592 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1594 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1595 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1596 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1598 ->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.")
1599 ->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.")
1600 ->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.")
1601 ->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.");
1602 sink.para()->text(L"Examples:");
1603 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1605 L">> MIXER 1-0 CLIP\n"
1606 L"<< 201 MIXER OK\n"
1607 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1610 std::wstring mixer_clip_command(command_context& ctx)
1612 if (ctx.parameters.empty())
1614 auto transform = get_current_transform(ctx).image_transform;
1615 auto translation = transform.clip_translation;
1616 auto scale = transform.clip_scale;
1618 return L"201 MIXER OK\r\n"
1619 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1620 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1621 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1622 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1625 transforms_applier transforms(ctx);
1626 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1627 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1628 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1629 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1630 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1631 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1633 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1635 transform.image_transform.clip_translation[0] = x;
1636 transform.image_transform.clip_translation[1] = y;
1637 transform.image_transform.clip_scale[0] = x_s;
1638 transform.image_transform.clip_scale[1] = y_s;
1640 }, duration, tween));
1643 return L"202 MIXER OK\r\n";
1646 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1648 sink.short_description(L"Change the anchor point of a layer.");
1649 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1650 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1652 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1653 ->text(L" will be done from.");
1655 ->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.")
1656 ->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.");
1657 sink.para()->text(L"Examples:");
1658 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1660 L">> MIXER 1-10 ANCHOR\n"
1661 L"<< 201 MIXER OK\n"
1662 L"<< 0.5 0.6", L"gets the anchor point");
1665 std::wstring mixer_anchor_command(command_context& ctx)
1667 if (ctx.parameters.empty())
1669 auto transform = get_current_transform(ctx).image_transform;
1670 auto anchor = transform.anchor;
1671 return L"201 MIXER OK\r\n"
1672 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1673 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1676 transforms_applier transforms(ctx);
1677 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1678 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1679 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1680 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1682 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1684 transform.image_transform.anchor[0] = x;
1685 transform.image_transform.anchor[1] = y;
1687 }, duration, tween));
1690 return L"202 MIXER OK\r\n";
1693 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1695 sink.short_description(L"Crop a layer.");
1696 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);
1698 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1699 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1700 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1702 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1703 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1704 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1705 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1706 sink.para()->text(L"Examples:");
1707 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");
1709 L">> MIXER 1-0 CROP\n"
1710 L"<< 201 MIXER OK\n"
1711 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1714 std::wstring mixer_crop_command(command_context& ctx)
1716 if (ctx.parameters.empty())
1718 auto crop = get_current_transform(ctx).image_transform.crop;
1719 return L"201 MIXER OK\r\n"
1720 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1721 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1722 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1723 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1726 transforms_applier transforms(ctx);
1727 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1728 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1729 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1730 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1731 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1732 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1734 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1736 transform.image_transform.crop.ul[0] = ul_x;
1737 transform.image_transform.crop.ul[1] = ul_y;
1738 transform.image_transform.crop.lr[0] = lr_x;
1739 transform.image_transform.crop.lr[1] = lr_y;
1741 }, duration, tween));
1744 return L"202 MIXER OK\r\n";
1747 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1749 sink.short_description(L"Rotate a layer.");
1750 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1752 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1753 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1754 sink.para()->text(L"Examples:");
1755 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1757 L">> MIXER 1-0 ROTATION\n"
1758 L"<< 201 MIXER OK\n"
1759 L"<< 45", L"to retrieve the current angle");
1762 std::wstring mixer_rotation_command(command_context& ctx)
1764 static const double PI = 3.141592653589793;
1766 return single_double_animatable_mixer_command(
1768 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1769 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1772 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1774 sink.short_description(L"Adjust the perspective transform of a layer.");
1775 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);
1777 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1779 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1780 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1781 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1782 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1783 sink.para()->text(L"Examples:");
1784 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1786 L">> MIXER 1-10 PERSPECTIVE\n"
1787 L"<< 201 MIXER OK\n"
1788 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1791 std::wstring mixer_perspective_command(command_context& ctx)
1793 if (ctx.parameters.empty())
1795 auto perspective = get_current_transform(ctx).image_transform.perspective;
1798 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1799 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1800 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1801 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1802 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1803 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1804 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1805 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1808 transforms_applier transforms(ctx);
1809 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1810 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1811 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1812 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1813 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1814 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1815 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1816 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1817 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1818 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1820 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1822 transform.image_transform.perspective.ul[0] = ul_x;
1823 transform.image_transform.perspective.ul[1] = ul_y;
1824 transform.image_transform.perspective.ur[0] = ur_x;
1825 transform.image_transform.perspective.ur[1] = ur_y;
1826 transform.image_transform.perspective.lr[0] = lr_x;
1827 transform.image_transform.perspective.lr[1] = lr_y;
1828 transform.image_transform.perspective.ll[0] = ll_x;
1829 transform.image_transform.perspective.ll[1] = ll_y;
1831 }, duration, tween));
1834 return L"202 MIXER OK\r\n";
1837 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1839 sink.short_description(L"Enable or disable mipmapping for a layer.");
1840 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1842 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1843 ->text(L"If no argument is given the current state is returned.");
1844 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1845 sink.para()->text(L"Examples:");
1846 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1848 L">> MIXER 1-10 MIPMAP\n"
1849 L"<< 201 MIXER OK\n"
1850 L"<< 1", L"for getting the current state");
1853 std::wstring mixer_mipmap_command(command_context& ctx)
1855 if (ctx.parameters.empty())
1856 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1858 transforms_applier transforms(ctx);
1859 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1860 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1862 transform.image_transform.use_mipmap = value;
1864 }, 0, tweener(L"linear")));
1867 return L"202 MIXER OK\r\n";
1870 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1872 sink.short_description(L"Change the volume of a layer.");
1873 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1874 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1875 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1876 sink.para()->text(L"Examples:");
1877 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1878 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1880 L">> MIXER 1-0 VOLUME\n"
1881 L"<< 201 MIXER OK\n"
1882 L"<< 0.8", L"to retrieve the current volume");
1885 std::wstring mixer_volume_command(command_context& ctx)
1887 return single_double_animatable_mixer_command(
1889 [](const frame_transform& t) { return t.audio_transform.volume; },
1890 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1893 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1895 sink.short_description(L"Change the volume of an entire channel.");
1896 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1897 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1898 sink.para()->text(L"Examples:");
1899 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1900 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1901 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1904 std::wstring mixer_mastervolume_command(command_context& ctx)
1906 if (ctx.parameters.empty())
1908 auto volume = ctx.channel.channel->mixer().get_master_volume();
1909 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1912 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1913 ctx.channel.channel->mixer().set_master_volume(master_volume);
1915 return L"202 MIXER OK\r\n";
1918 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1920 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1921 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1922 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1923 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1924 sink.para()->text(L"Examples:");
1925 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1926 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1928 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1929 L"<< 201 MIXER OK\n"
1933 std::wstring mixer_straight_alpha_command(command_context& ctx)
1935 if (ctx.parameters.empty())
1937 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1938 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1941 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1942 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1944 return L"202 MIXER OK\r\n";
1947 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1949 sink.short_description(L"Create a grid of video layers.");
1950 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1952 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1953 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1954 sink.para()->text(L"Examples:");
1955 sink.example(L">> MIXER 1 GRID 2");
1958 std::wstring mixer_grid_command(command_context& ctx)
1960 transforms_applier transforms(ctx);
1961 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1962 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1963 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1964 double delta = 1.0 / static_cast<double>(n);
1965 for (int x = 0; x < n; ++x)
1967 for (int y = 0; y < n; ++y)
1969 int index = x + y*n + 1;
1970 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1972 transform.image_transform.fill_translation[0] = x*delta;
1973 transform.image_transform.fill_translation[1] = y*delta;
1974 transform.image_transform.fill_scale[0] = delta;
1975 transform.image_transform.fill_scale[1] = delta;
1976 transform.image_transform.clip_translation[0] = x*delta;
1977 transform.image_transform.clip_translation[1] = y*delta;
1978 transform.image_transform.clip_scale[0] = delta;
1979 transform.image_transform.clip_scale[1] = delta;
1981 }, duration, tween));
1986 return L"202 MIXER OK\r\n";
1989 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1991 sink.short_description(L"Commit all deferred mixer transforms.");
1992 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1993 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1994 sink.para()->text(L"Examples:");
1996 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1997 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1998 L">> MIXER 1 COMMIT");
2001 std::wstring mixer_commit_command(command_context& ctx)
2003 transforms_applier transforms(ctx);
2004 transforms.commit_deferred();
2006 return L"202 MIXER OK\r\n";
2009 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
2011 sink.short_description(L"Clear all transformations on a channel or layer.");
2012 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
2013 sink.para()->text(L"Clears all transformations on a channel or layer.");
2014 sink.para()->text(L"Examples:");
2015 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
2016 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
2019 std::wstring mixer_clear_command(command_context& ctx)
2021 int layer = ctx.layer_id;
2024 ctx.channel.channel->stage().clear_transforms();
2026 ctx.channel.channel->stage().clear_transforms(layer);
2028 return L"202 MIXER OK\r\n";
2031 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2033 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2034 sink.syntax(L"CHANNEL_GRID");
2035 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2037 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2038 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2041 std::wstring channel_grid_command(command_context& ctx)
2044 auto self = ctx.channels.back();
2046 core::diagnostics::scoped_call_context save;
2047 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2049 std::vector<std::wstring> params;
2050 params.push_back(L"SCREEN");
2051 params.push_back(L"0");
2052 params.push_back(L"NAME");
2053 params.push_back(L"Channel Grid Window");
2054 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage(), get_channels(ctx));
2056 self.channel->output().add(screen);
2058 for (auto& channel : ctx.channels)
2060 if (channel.channel != self.channel)
2062 core::diagnostics::call_context::for_thread().layer = index;
2063 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2064 self.channel->stage().load(index, producer, false);
2065 self.channel->stage().play(index);
2070 auto num_channels = ctx.channels.size() - 1;
2071 int square_side_length = std::ceil(std::sqrt(num_channels));
2073 ctx.channel_index = self.channel->index();
2075 ctx.parameters.clear();
2076 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2077 mixer_grid_command(ctx);
2079 return L"202 CHANNEL_GRID OK\r\n";
2082 // Thumbnail Commands
2084 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2086 sink.short_description(L"List all thumbnails.");
2087 sink.syntax(L"THUMBNAIL LIST");
2088 sink.para()->text(L"Lists all thumbnails.");
2089 sink.para()->text(L"Examples:");
2091 L">> THUMBNAIL LIST\n"
2092 L"<< 200 THUMBNAIL LIST OK\n"
2093 L"<< \"AMB\" 20130301T124409 1149\n"
2094 L"<< \"foo/bar\" 20130523T234001 244");
2097 std::wstring thumbnail_list_command(command_context& ctx)
2099 std::wstringstream replyString;
2100 replyString << L"200 THUMBNAIL LIST OK\r\n";
2102 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2104 if (boost::filesystem::is_regular_file(itr->path()))
2106 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2109 auto relativePath = get_relative_without_extension(itr->path(), env::thumbnails_folder());
2110 auto str = relativePath.generic_wstring();
2112 if (str[0] == '\\' || str[0] == '/')
2113 str = std::wstring(str.begin() + 1, str.end());
2115 auto mtime = boost::filesystem::last_write_time(itr->path());
2116 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2117 auto file_size = boost::filesystem::file_size(itr->path());
2119 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2123 replyString << L"\r\n";
2125 return boost::to_upper_copy(replyString.str());
2128 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2130 sink.short_description(L"Retrieve a thumbnail.");
2131 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2132 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2133 sink.para()->text(L"Examples:");
2135 L">> THUMBNAIL RETRIEVE foo/bar\n"
2136 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2137 L"<< ...base64 data...");
2140 std::wstring thumbnail_retrieve_command(command_context& ctx)
2142 std::wstring filename = env::thumbnails_folder();
2143 filename.append(ctx.parameters.at(0));
2144 filename.append(L".png");
2146 std::wstring file_contents;
2148 auto found_file = find_case_insensitive(filename);
2151 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2153 if (file_contents.empty())
2154 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2156 std::wstringstream reply;
2158 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2159 reply << file_contents;
2164 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2166 sink.short_description(L"Regenerate a thumbnail.");
2167 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2168 sink.para()->text(L"Regenerates a thumbnail.");
2171 std::wstring thumbnail_generate_command(command_context& ctx)
2175 ctx.thumb_gen->generate(ctx.parameters.at(0));
2176 return L"202 THUMBNAIL GENERATE OK\r\n";
2179 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2182 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2184 sink.short_description(L"Regenerate all thumbnails.");
2185 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2186 sink.para()->text(L"Regenerates all thumbnails.");
2189 std::wstring thumbnail_generateall_command(command_context& ctx)
2193 ctx.thumb_gen->generate_all();
2194 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2197 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2202 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2204 sink.short_description(L"Get information about a media file.");
2205 sink.syntax(L"CINF [filename:string]");
2206 sink.para()->text(L"Returns information about a media file.");
2209 std::wstring cinf_command(command_context& ctx)
2212 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2214 auto path = itr->path();
2215 auto file = path.replace_extension(L"").filename().wstring();
2216 if (boost::iequals(file, ctx.parameters.at(0)))
2217 info += MediaInfo(itr->path(), ctx.media_info_repo);
2221 CASPAR_THROW_EXCEPTION(file_not_found());
2223 std::wstringstream replyString;
2224 replyString << L"200 CINF OK\r\n";
2225 replyString << info << "\r\n";
2227 return replyString.str();
2230 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2232 sink.short_description(L"List all media files.");
2233 sink.syntax(L"CLS");
2235 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2236 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2239 std::wstring cls_command(command_context& ctx)
2241 std::wstringstream replyString;
2242 replyString << L"200 CLS OK\r\n";
2243 replyString << ListMedia(ctx.media_info_repo);
2244 replyString << L"\r\n";
2245 return boost::to_upper_copy(replyString.str());
2248 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2250 sink.short_description(L"List all fonts.");
2251 sink.syntax(L"FLS");
2253 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2254 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2255 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2258 std::wstring fls_command(command_context& ctx)
2260 std::wstringstream replyString;
2261 replyString << L"200 FLS OK\r\n";
2263 for (auto& font : core::text::list_fonts())
2264 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2266 replyString << L"\r\n";
2268 return replyString.str();
2271 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2273 sink.short_description(L"List all templates.");
2274 sink.syntax(L"TLS");
2276 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2277 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2280 std::wstring tls_command(command_context& ctx)
2282 std::wstringstream replyString;
2283 replyString << L"200 TLS OK\r\n";
2285 replyString << ListTemplates(ctx.cg_registry);
2286 replyString << L"\r\n";
2288 return replyString.str();
2291 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2293 sink.short_description(L"Get version information.");
2294 sink.syntax(L"VERSION {[component:string]}");
2295 sink.para()->text(L"Returns the version of specified component.");
2296 sink.para()->text(L"Examples:");
2299 L"<< 201 VERSION OK\n"
2300 L"<< 2.1.0.f207a33 STABLE");
2302 L">> VERSION SERVER\n"
2303 L"<< 201 VERSION OK\n"
2304 L"<< 2.1.0.f207a33 STABLE");
2306 L">> VERSION FLASH\n"
2307 L"<< 201 VERSION OK\n"
2310 L">> VERSION TEMPLATEHOST\n"
2311 L"<< 201 VERSION OK\n"
2315 L"<< 201 VERSION OK\n"
2319 std::wstring version_command(command_context& ctx)
2321 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2323 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2325 return L"201 VERSION OK\r\n" + version + L"\r\n";
2328 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2331 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2333 sink.short_description(L"Get a list of the available channels.");
2334 sink.syntax(L"INFO");
2335 sink.para()->text(L"Retrieves a list of the available channels.");
2339 L"<< 1 720p5000 PLAYING\n"
2340 L"<< 2 PAL PLAYING");
2343 std::wstring info_command(command_context& ctx)
2345 std::wstringstream replyString;
2346 // This is needed for backwards compatibility with old clients
2347 replyString << L"200 INFO OK\r\n";
2348 for (size_t n = 0; n < ctx.channels.size(); ++n)
2349 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2350 replyString << L"\r\n";
2351 return replyString.str();
2354 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2356 std::wstringstream replyString;
2358 if (command.empty())
2359 replyString << L"201 INFO OK\r\n";
2361 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2363 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2364 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2365 replyString << L"\r\n";
2366 return replyString.str();
2369 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2371 sink.short_description(L"Get information about a channel or a layer.");
2372 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2373 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2374 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2377 std::wstring info_channel_command(command_context& ctx)
2379 boost::property_tree::wptree info;
2380 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2382 if (layer == std::numeric_limits<int>::min())
2384 info.add_child(L"channel", ctx.channel.channel->info())
2385 .add(L"index", ctx.channel_index);
2389 if (ctx.parameters.size() >= 1)
2391 if (boost::iequals(ctx.parameters.at(0), L"B"))
2392 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2394 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2398 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2402 return create_info_xml_reply(info);
2405 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2407 sink.short_description(L"Get information about a template.");
2408 sink.syntax(L"INFO TEMPLATE [template:string]");
2409 sink.para()->text(L"Gets information about the specified template.");
2412 std::wstring info_template_command(command_context& ctx)
2414 auto filename = ctx.parameters.at(0);
2416 std::wstringstream str;
2417 str << u16(ctx.cg_registry->read_meta_info(filename));
2418 boost::property_tree::wptree info;
2419 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2421 return create_info_xml_reply(info, L"TEMPLATE");
2424 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2426 sink.short_description(L"Get the contents of the configuration used.");
2427 sink.syntax(L"INFO CONFIG");
2428 sink.para()->text(L"Gets the contents of the configuration used.");
2431 std::wstring info_config_command(command_context& ctx)
2433 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2436 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2438 sink.short_description(L"Get information about the paths used.");
2439 sink.syntax(L"INFO PATHS");
2440 sink.para()->text(L"Gets information about the paths used.");
2443 std::wstring info_paths_command(command_context& ctx)
2445 boost::property_tree::wptree info;
2446 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2447 info.add(L"paths.initial-path", caspar::env::initial_folder() + L"/");
2449 return create_info_xml_reply(info, L"PATHS");
2452 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2454 sink.short_description(L"Get system information.");
2455 sink.syntax(L"INFO SYSTEM");
2456 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2459 std::wstring info_system_command(command_context& ctx)
2461 boost::property_tree::wptree info;
2463 info.add(L"system.name", caspar::system_product_name());
2464 info.add(L"system.os.description", caspar::os_description());
2465 info.add(L"system.cpu", caspar::cpu_info());
2467 ctx.system_info_repo->fill_information(info);
2469 return create_info_xml_reply(info, L"SYSTEM");
2472 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2474 sink.short_description(L"Get detailed information about all channels.");
2475 sink.syntax(L"INFO SERVER");
2476 sink.para()->text(L"Gets detailed information about all channels.");
2479 std::wstring info_server_command(command_context& ctx)
2481 boost::property_tree::wptree info;
2484 for (auto& channel : ctx.channels)
2485 info.add_child(L"channels.channel", channel.channel->info())
2486 .add(L"index", ++index);
2488 return create_info_xml_reply(info, L"SERVER");
2491 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2493 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2494 sink.syntax(L"INFO QUEUES");
2495 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2498 std::wstring info_queues_command(command_context& ctx)
2500 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2503 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2505 sink.short_description(L"Lists all known threads in the server.");
2506 sink.syntax(L"INFO THREADS");
2507 sink.para()->text(L"Lists all known threads in the server.");
2510 std::wstring info_threads_command(command_context& ctx)
2512 std::wstringstream replyString;
2513 replyString << L"200 INFO THREADS OK\r\n";
2515 for (auto& thread : get_thread_infos())
2517 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2520 replyString << L"\r\n";
2521 return replyString.str();
2524 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2526 sink.short_description(L"Get the current delay on a channel or a layer.");
2527 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2528 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2531 std::wstring info_delay_command(command_context& ctx)
2533 boost::property_tree::wptree info;
2534 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2536 if (layer == std::numeric_limits<int>::min())
2537 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2539 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2540 .add(L"index", layer);
2542 return create_info_xml_reply(info, L"DELAY");
2545 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2547 sink.short_description(L"Open the diagnostics window.");
2548 sink.syntax(L"DIAG");
2549 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2552 std::wstring diag_command(command_context& ctx)
2554 core::diagnostics::osd::show_graphs(true);
2556 return L"202 DIAG OK\r\n";
2559 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2561 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2562 sink.syntax(L"GL INFO");
2563 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2566 std::wstring gl_info_command(command_context& ctx)
2568 auto device = ctx.ogl_device;
2571 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2573 std::wstringstream result;
2574 result << L"201 GL INFO OK\r\n";
2576 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2577 auto info = device->info();
2578 boost::property_tree::write_xml(result, info, w);
2581 return result.str();
2584 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2586 sink.short_description(L"Release pooled OpenGL resources.");
2587 sink.syntax(L"GL GC");
2588 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2591 std::wstring gl_gc_command(command_context& ctx)
2593 auto device = ctx.ogl_device;
2596 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2598 device->gc().wait();
2600 return L"202 GL GC OK\r\n";
2603 static const int WIDTH = 80;
2605 struct max_width_sink : public core::help_sink
2607 std::size_t max_width = 0;
2609 void begin_item(const std::wstring& name) override
2611 max_width = std::max(name.length(), max_width);
2615 struct short_description_sink : public core::help_sink
2618 std::wstringstream& out;
2620 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2622 void begin_item(const std::wstring& name) override
2624 out << std::left << std::setw(width + 1) << name;
2627 void short_description(const std::wstring& short_description) override
2629 out << short_description << L"\r\n";
2633 struct simple_paragraph_builder : core::paragraph_builder
2635 std::wostringstream out;
2636 std::wstringstream& commit_to;
2638 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2639 ~simple_paragraph_builder()
2641 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2643 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2645 out << std::move(text);
2646 return shared_from_this();
2648 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2649 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2650 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2651 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2654 struct simple_definition_list_builder : core::definition_list_builder
2656 std::wstringstream& out;
2658 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2659 ~simple_definition_list_builder()
2664 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2666 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2667 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2668 return shared_from_this();
2672 struct long_description_sink : public core::help_sink
2674 std::wstringstream& out;
2676 long_description_sink(std::wstringstream& out) : out(out) { }
2678 void syntax(const std::wstring& syntax) override
2680 out << L"Syntax:\n";
2681 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2684 spl::shared_ptr<core::paragraph_builder> para() override
2686 return spl::make_shared<simple_paragraph_builder>(out);
2689 spl::shared_ptr<core::definition_list_builder> definitions() override
2691 return spl::make_shared<simple_definition_list_builder>(out);
2694 void example(const std::wstring& code, const std::wstring& caption) override
2696 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2698 if (!caption.empty())
2699 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2704 void begin_item(const std::wstring& name) override
2706 out << name << L"\n\n";
2710 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2712 std::wstringstream result;
2713 result << L"200 " << help_command << L" OK\r\n";
2714 max_width_sink width;
2715 ctx.help_repo->help(tags, width);
2716 short_description_sink sink(width.max_width, result);
2717 sink.width = width.max_width;
2718 ctx.help_repo->help(tags, sink);
2720 return result.str();
2723 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2725 std::wstringstream result;
2726 result << L"201 " << help_command << L" OK\r\n";
2727 auto joined = boost::join(ctx.parameters, L" ");
2728 long_description_sink sink(result);
2729 ctx.help_repo->help(tags, joined, sink);
2731 return result.str();
2734 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2736 sink.short_description(L"Show online help for AMCP commands.");
2737 sink.syntax(LR"(HELP {[command:string]})");
2738 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2739 sink.example(L">> HELP", L"Shows a list of commands.");
2740 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2743 std::wstring help_command(command_context& ctx)
2745 if (ctx.parameters.size() == 0)
2746 return create_help_list(L"HELP", ctx, { L"AMCP" });
2748 return create_help_details(L"HELP", ctx, { L"AMCP" });
2751 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2753 sink.short_description(L"Show online help for producers.");
2754 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2755 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2756 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2757 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2760 std::wstring help_producer_command(command_context& ctx)
2762 if (ctx.parameters.size() == 0)
2763 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2765 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2768 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2770 sink.short_description(L"Show online help for consumers.");
2771 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2772 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2773 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2774 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2777 std::wstring help_consumer_command(command_context& ctx)
2779 if (ctx.parameters.size() == 0)
2780 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2782 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2785 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2787 sink.short_description(L"Disconnect the session.");
2788 sink.syntax(L"BYE");
2790 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2791 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2794 std::wstring bye_command(command_context& ctx)
2796 ctx.client->disconnect();
2800 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2802 sink.short_description(L"Shutdown the server.");
2803 sink.syntax(L"KILL");
2804 sink.para()->text(L"Shuts the server down.");
2807 std::wstring kill_command(command_context& ctx)
2809 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2810 return L"202 KILL OK\r\n";
2813 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2815 sink.short_description(L"Shutdown the server with restart exit code.");
2816 sink.syntax(L"RESTART");
2818 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2819 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2822 std::wstring restart_command(command_context& ctx)
2824 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2825 return L"202 RESTART OK\r\n";
2828 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2830 sink.short_description(L"Lock or unlock access to a channel.");
2831 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2832 sink.para()->text(L"Allows for exclusive access to a channel.");
2833 sink.para()->text(L"Examples:");
2834 sink.example(L"LOCK 1 ACQUIRE secret");
2835 sink.example(L"LOCK 1 RELEASE");
2836 sink.example(L"LOCK 1 CLEAR");
2839 std::wstring lock_command(command_context& ctx)
2841 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2842 auto lock = ctx.channels.at(channel_index).lock;
2843 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2845 if (command == L"ACQUIRE")
2847 std::wstring lock_phrase = ctx.parameters.at(2);
2849 //TODO: read options
2851 //just lock one channel
2852 if (!lock->try_lock(lock_phrase, ctx.client))
2853 return L"503 LOCK ACQUIRE FAILED\r\n";
2855 return L"202 LOCK ACQUIRE OK\r\n";
2857 else if (command == L"RELEASE")
2859 lock->release_lock(ctx.client);
2860 return L"202 LOCK RELEASE OK\r\n";
2862 else if (command == L"CLEAR")
2864 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2865 std::wstring client_override_phrase;
2867 if (!override_phrase.empty())
2868 client_override_phrase = ctx.parameters.at(2);
2870 //just clear one channel
2871 if (client_override_phrase != override_phrase)
2872 return L"503 LOCK CLEAR FAILED\r\n";
2874 lock->clear_locks();
2876 return L"202 LOCK CLEAR OK\r\n";
2879 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2882 void register_commands(amcp_command_repository& repo)
2884 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2885 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2886 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2887 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2888 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2889 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2890 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2891 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2892 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2893 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2894 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2895 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2896 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2897 repo.register_command( L"Basic Commands", L"LOG CATEGORY", log_category_describer, log_category_command, 2);
2898 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2899 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2901 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2902 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2903 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2904 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2906 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2907 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2908 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2909 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2910 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2911 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2912 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2913 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2914 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2916 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2917 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2918 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2919 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2920 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2921 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2922 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2923 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2924 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2925 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2926 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2927 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2928 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2929 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2930 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2931 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2932 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2933 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2934 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2935 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2936 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2937 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2939 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2940 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2941 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2942 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2944 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2945 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2946 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
2947 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2948 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2949 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2950 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2951 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2952 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2953 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2954 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2955 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2956 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2957 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2958 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2959 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2960 repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
2961 repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
2962 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2963 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2964 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2965 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2966 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2967 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2971 }} //namespace caspar