2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Nicklas P Andersson
22 #include "../StdAfx.h"
25 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
28 #include "AMCPCommandsImpl.h"
30 #include "amcp_command_repository.h"
31 #include "AMCPCommandQueue.h"
33 #include <common/env.h>
35 #include <common/log.h>
36 #include <common/param.h>
37 #include <common/os/system_info.h>
38 #include <common/os/filesystem.h>
39 #include <common/base64.h>
40 #include <common/thread_info.h>
41 #include <common/filesystem.h>
43 #include <core/producer/cg_proxy.h>
44 #include <core/producer/frame_producer.h>
45 #include <core/help/help_repository.h>
46 #include <core/help/help_sink.h>
47 #include <core/help/util.h>
48 #include <core/video_format.h>
49 #include <core/producer/transition/transition_producer.h>
50 #include <core/frame/audio_channel_layout.h>
51 #include <core/frame/frame_transform.h>
52 #include <core/producer/text/text_producer.h>
53 #include <core/producer/stage.h>
54 #include <core/producer/layer.h>
55 #include <core/mixer/mixer.h>
56 #include <core/consumer/output.h>
57 #include <core/thumbnail_generator.h>
58 #include <core/producer/media_info/media_info.h>
59 #include <core/producer/media_info/media_info_repository.h>
60 #include <core/diagnostics/call_context.h>
61 #include <core/diagnostics/osd_graph.h>
62 #include <core/system_info_provider.h>
71 #include <boost/date_time/posix_time/posix_time.hpp>
72 #include <boost/lexical_cast.hpp>
73 #include <boost/algorithm/string.hpp>
74 #include <boost/filesystem.hpp>
75 #include <boost/filesystem/fstream.hpp>
76 #include <boost/regex.hpp>
77 #include <boost/property_tree/xml_parser.hpp>
78 #include <boost/locale.hpp>
79 #include <boost/range/adaptor/transformed.hpp>
80 #include <boost/range/algorithm/copy.hpp>
81 #include <boost/archive/iterators/base64_from_binary.hpp>
82 #include <boost/archive/iterators/insert_linebreaks.hpp>
83 #include <boost/archive/iterators/transform_width.hpp>
85 #include <tbb/concurrent_unordered_map.h>
89 102 [action] Information that [action] has happened
90 101 [action] Information that [action] has happened plus one row of data
92 202 [command] OK [command] has been executed
93 201 [command] OK [command] has been executed, plus one row of data
94 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
96 400 ERROR the command could not be understood
97 401 [command] ERROR invalid/missing channel
98 402 [command] ERROR parameter missing
99 403 [command] ERROR invalid parameter
100 404 [command] ERROR file not found
102 500 FAILED internal error
103 501 [command] FAILED internal error
104 502 [command] FAILED could not read file
105 503 [command] FAILED access denied
107 600 [command] FAILED [command] not implemented
110 namespace caspar { namespace protocol { namespace amcp {
112 using namespace core;
114 std::wstring read_file_base64(const boost::filesystem::path& file)
116 using namespace boost::archive::iterators;
118 boost::filesystem::ifstream filestream(file, std::ios::binary);
123 auto length = boost::filesystem::file_size(file);
124 std::vector<char> bytes;
125 bytes.resize(length);
126 filestream.read(bytes.data(), length);
128 std::string result(to_base64(bytes.data(), length));
129 return std::wstring(result.begin(), result.end());
132 std::wstring read_utf8_file(const boost::filesystem::path& file)
134 std::wstringstream result;
135 boost::filesystem::wifstream filestream(file);
142 result << filestream.rdbuf();
148 std::wstring read_latin1_file(const boost::filesystem::path& file)
150 boost::locale::generator gen;
151 gen.locale_cache_enabled(true);
152 gen.categories(boost::locale::codepage_facet);
154 std::stringstream result_stream;
155 boost::filesystem::ifstream filestream(file);
156 filestream.imbue(gen("en_US.ISO8859-1"));
161 result_stream << filestream.rdbuf();
164 std::string result = result_stream.str();
165 std::wstring widened_result;
167 // The first 255 codepoints in unicode is the same as in latin1
169 result | boost::adaptors::transformed(
170 [](char c) { return static_cast<unsigned char>(c); }),
171 std::back_inserter(widened_result));
173 return widened_result;
176 std::wstring read_file(const boost::filesystem::path& file)
178 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
180 if (!boost::filesystem::exists(file))
185 if (boost::filesystem::file_size(file) >= 3)
187 boost::filesystem::ifstream bom_stream(file);
190 bom_stream.read(header, 3);
193 if (std::memcmp(BOM, header, 3) == 0)
194 return read_utf8_file(file);
197 return read_latin1_file(file);
200 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
202 if (!boost::filesystem::is_regular_file(path))
205 auto media_info = media_info_repo->get(path.wstring());
210 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
212 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
213 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
214 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
216 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
217 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
218 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
220 auto relativePath = get_relative_without_extension(path, env::media_folder());
221 auto str = relativePath.generic_wstring();
223 if (str[0] == '\\' || str[0] == '/')
224 str = std::wstring(str.begin() + 1, str.end());
226 return std::wstring()
228 + L"\" " + media_info->clip_type +
230 + L" " + writeTimeWStr +
231 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
232 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
236 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
238 std::wstringstream replyString;
239 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
240 replyString << MediaInfo(itr->path(), media_info_repo);
242 return boost::to_upper_copy(replyString.str());
245 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
247 std::wstringstream replyString;
249 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
251 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
253 auto relativePath = get_relative_without_extension(itr->path(), env::template_folder());
255 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
256 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
257 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
259 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
260 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
262 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
264 auto dir = relativePath.parent_path();
265 auto file = boost::to_upper_copy(relativePath.filename().wstring());
266 relativePath = dir / file;
268 auto str = relativePath.generic_wstring();
269 boost::trim_if(str, boost::is_any_of("\\/"));
271 auto template_type = cg_registry->get_cg_producer_name(str);
273 replyString << L"\"" << str
274 << L"\" " << sizeWStr
275 << L" " << writeTimeWStr
276 << L" " << template_type
280 return replyString.str();
283 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
285 return core::frame_producer_dependencies(
286 channel->frame_factory(),
287 cpplinq::from(ctx.channels)
288 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
290 channel->video_format_desc(),
291 ctx.producer_registry);
296 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
298 sink.short_description(L"Load a media file or resource in the background.");
299 sink.syntax(LR"(LOADBG [channel:int]{-[layer:int]} [clip:string] {[loop:LOOP]} {[transition:CUT,MIX,PUSH,WIPE,SLIDE] [duration:int] {[tween:string]|linear} {[direction:LEFT,RIGHT]|RIGHT}|CUT 0} {SEEK [frame:int]} {LENGTH [frames:int]} {FILTER [filter:string]} {[auto:AUTO]})");
301 ->text(L"Loads a producer in the background and prepares it for playout. ")
302 ->text(L"If no layer is specified the default layer index will be used.");
304 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
305 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
307 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
308 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
310 ->code(L"loop")->text(L" will cause the clip to loop.");
312 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
314 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
316 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
317 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
318 sink.para()->text(L"Examples:");
319 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
320 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
321 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
322 sink.example(L">> LOADBG 1-0 MY_FILE");
324 L">> PLAY 1-1 MY_FILE\n"
325 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
326 L"To automatically fade out a layer after a video file has been played to the end");
328 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
330 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
331 ->code(L"filter")->text(L" command.");
334 std::wstring loadbg_command(command_context& ctx)
336 transition_info transitionInfo;
340 std::wstring message;
341 for (size_t n = 0; n < ctx.parameters.size(); ++n)
342 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
344 static const boost::wregex expr(LR"(.*(?<TRANSITION>CUT|PUSH|SLIDE|WIPE|MIX)\s*(?<DURATION>\d+)\s*(?<TWEEN>(LINEAR)|(EASE[^\s]*))?\s*(?<DIRECTION>FROMLEFT|FROMRIGHT|LEFT|RIGHT)?.*)");
346 if (boost::regex_match(message, what, expr))
348 auto transition = what["TRANSITION"].str();
349 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
350 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
351 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
352 transitionInfo.tweener = tween;
354 if (transition == L"CUT")
355 transitionInfo.type = transition_type::cut;
356 else if (transition == L"MIX")
357 transitionInfo.type = transition_type::mix;
358 else if (transition == L"PUSH")
359 transitionInfo.type = transition_type::push;
360 else if (transition == L"SLIDE")
361 transitionInfo.type = transition_type::slide;
362 else if (transition == L"WIPE")
363 transitionInfo.type = transition_type::wipe;
365 if (direction == L"FROMLEFT")
366 transitionInfo.direction = transition_direction::from_left;
367 else if (direction == L"FROMRIGHT")
368 transitionInfo.direction = transition_direction::from_right;
369 else if (direction == L"LEFT")
370 transitionInfo.direction = transition_direction::from_right;
371 else if (direction == L"RIGHT")
372 transitionInfo.direction = transition_direction::from_left;
375 //Perform loading of the clip
376 core::diagnostics::scoped_call_context save;
377 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
378 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
380 auto channel = ctx.channel.channel;
381 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
383 if (pFP == frame_producer::empty())
384 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
386 bool auto_play = contains_param(L"AUTO", ctx.parameters);
388 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
390 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
392 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
394 return L"202 LOADBG OK\r\n";
397 void load_describer(core::help_sink& sink, const core::help_repository& repo)
399 sink.short_description(L"Load a media file or resource to the foreground.");
400 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
402 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
403 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
404 sink.para()->text(L"Examples:");
405 sink.example(L">> LOAD 1 MY_FILE");
406 sink.example(L">> LOAD 1-1 MY_FILE");
407 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
410 std::wstring load_command(command_context& ctx)
412 core::diagnostics::scoped_call_context save;
413 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
414 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
415 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
416 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
418 return L"202 LOAD OK\r\n";
421 void play_describer(core::help_sink& sink, const core::help_repository& repository)
423 sink.short_description(L"Play a media file or resource.");
424 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
426 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
427 ->text(L") is prepared, it will be executed.");
429 ->text(L"If additional parameters (see ")->see(L"LOADBG")
430 ->text(L") are provided then the provided clip will first be loaded to the background.");
431 sink.para()->text(L"Examples:");
432 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
433 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
434 sink.example(L">> PLAY 1-0 MY_FILE");
435 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
438 std::wstring play_command(command_context& ctx)
440 if (!ctx.parameters.empty())
443 ctx.channel.channel->stage().play(ctx.layer_index());
445 return L"202 PLAY OK\r\n";
448 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
450 sink.short_description(L"Pause playback of a layer.");
451 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
453 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
454 ->text(L" command can be used to resume playback again.");
455 sink.para()->text(L"Examples:");
456 sink.example(L">> PAUSE 1");
457 sink.example(L">> PAUSE 1-1");
460 std::wstring pause_command(command_context& ctx)
462 ctx.channel.channel->stage().pause(ctx.layer_index());
463 return L"202 PAUSE OK\r\n";
466 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
468 sink.short_description(L"Resume playback of a layer.");
469 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
471 ->text(L"Resumes playback of a foreground clip previously paused with the ")
472 ->see(L"PAUSE")->text(L" command.");
473 sink.para()->text(L"Examples:");
474 sink.example(L">> RESUME 1");
475 sink.example(L">> RESUME 1-1");
478 std::wstring resume_command(command_context& ctx)
480 ctx.channel.channel->stage().resume(ctx.layer_index());
481 return L"202 RESUME OK\r\n";
484 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
486 sink.short_description(L"Remove the foreground clip of a layer.");
487 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
489 ->text(L"Removes the foreground clip of the specified layer.");
490 sink.para()->text(L"Examples:");
491 sink.example(L">> STOP 1");
492 sink.example(L">> STOP 1-1");
495 std::wstring stop_command(command_context& ctx)
497 ctx.channel.channel->stage().stop(ctx.layer_index());
498 return L"202 STOP OK\r\n";
501 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
503 sink.short_description(L"Remove all clips of a layer or an entire channel.");
504 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
506 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
507 ->text(L"If no layer is specified then all layers in the specified ")
508 ->code(L"video_channel")->text(L" are cleared.");
509 sink.para()->text(L"Examples:");
510 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
511 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
514 std::wstring clear_command(command_context& ctx)
516 int index = ctx.layer_index(std::numeric_limits<int>::min());
517 if (index != std::numeric_limits<int>::min())
518 ctx.channel.channel->stage().clear(index);
520 ctx.channel.channel->stage().clear();
522 return L"202 CLEAR OK\r\n";
525 void call_describer(core::help_sink& sink, const core::help_repository& repo)
527 sink.short_description(L"Call a method on a producer.");
528 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
530 ->text(L"Calls method on the specified producer with the provided ")
531 ->code(L"param")->text(L" string.");
532 sink.para()->text(L"Examples:");
533 sink.example(L">> CALL 1 LOOP");
534 sink.example(L">> CALL 1-2 SEEK 25");
537 std::wstring call_command(command_context& ctx)
539 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
541 // TODO: because of std::async deferred timed waiting does not work
543 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
544 if (wait_res == std::future_status::timeout)
545 CASPAR_THROW_EXCEPTION(timed_out());*/
547 std::wstringstream replyString;
548 if (result.get().empty())
549 replyString << L"202 CALL OK\r\n";
551 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
553 return replyString.str();
556 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
558 sink.short_description(L"Swap layers between channels.");
559 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
561 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
562 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
563 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
564 sink.para()->text(L"Examples:");
565 sink.example(L">> SWAP 1 2");
566 sink.example(L">> SWAP 1-1 2-3");
567 sink.example(L">> SWAP 1-1 1-2");
568 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
571 std::wstring swap_command(command_context& ctx)
573 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
575 if (ctx.layer_index(-1) != -1)
577 std::vector<std::string> strs;
578 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
580 auto ch1 = ctx.channel.channel;
581 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
583 int l1 = ctx.layer_index();
584 int l2 = boost::lexical_cast<int>(strs.at(1));
586 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
590 auto ch1 = ctx.channel.channel;
591 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
592 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
595 return L"202 SWAP OK\r\n";
598 void add_describer(core::help_sink& sink, const core::help_repository& repo)
600 sink.short_description(L"Add a consumer to a video channel.");
601 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
603 ->text(L"Adds a consumer to the specified video channel. The string ")
604 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
605 ->text(L"If a successful match is found a consumer will be created and added to the ")
606 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
607 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
608 ->see(L"the CasparCG config file")->text(L".");
609 sink.para()->text(L"Examples:");
610 sink.example(L">> ADD 1 DECKLINK 1");
611 sink.example(L">> ADD 1 BLUEFISH 2");
612 sink.example(L">> ADD 1 SCREEN");
613 sink.example(L">> ADD 1 AUDIO");
614 sink.example(L">> ADD 1 IMAGE filename");
615 sink.example(L">> ADD 1 FILE filename.mov");
616 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
617 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
618 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
621 std::wstring add_command(command_context& ctx)
623 replace_placeholders(
624 L"<CLIENT_IP_ADDRESS>",
625 ctx.client->address(),
628 core::diagnostics::scoped_call_context save;
629 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
631 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
632 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
634 return L"202 ADD OK\r\n";
637 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
639 sink.short_description(L"Remove a consumer from a video channel.");
640 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
642 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
643 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
644 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
645 sink.para()->text(L"Examples:");
646 sink.example(L">> REMOVE 1 DECKLINK 1");
647 sink.example(L">> REMOVE 1 BLUEFISH 2");
648 sink.example(L">> REMOVE 1 SCREEN");
649 sink.example(L">> REMOVE 1 AUDIO");
650 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
653 std::wstring remove_command(command_context& ctx)
655 auto index = ctx.layer_index(std::numeric_limits<int>::min());
657 if (index == std::numeric_limits<int>::min())
659 replace_placeholders(
660 L"<CLIENT_IP_ADDRESS>",
661 ctx.client->address(),
664 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
667 ctx.channel.channel->output().remove(index);
669 return L"202 REMOVE OK\r\n";
672 void print_describer(core::help_sink& sink, const core::help_repository& repo)
674 sink.short_description(L"Take a snapshot of a channel.");
675 sink.syntax(L"PRINT [video_channel:int]");
677 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
678 ->code(L"media")->text(L" folder.");
679 sink.para()->text(L"Examples:");
680 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
683 std::wstring print_command(command_context& ctx)
685 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
687 return L"202 PRINT OK\r\n";
690 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
692 sink.short_description(L"Change the log level of the server.");
693 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
694 sink.para()->text(L"Changes the log level of the server.");
695 sink.para()->text(L"Examples:");
696 sink.example(L">> LOG LEVEL trace");
697 sink.example(L">> LOG LEVEL info");
700 std::wstring log_level_command(command_context& ctx)
702 log::set_log_level(ctx.parameters.at(0));
704 return L"202 LOG OK\r\n";
707 void log_category_describer(core::help_sink& sink, const core::help_repository& repo)
709 sink.short_description(L"Enable/disable a logging category in the server.");
710 sink.syntax(L"LOG CATEGORY [category:calltrace,communication] [enable:0,1]");
711 sink.para()->text(L"Enables or disables the specified logging category.");
712 sink.para()->text(L"Examples:");
713 sink.example(L">> LOG CATEGORY calltrace 1", L"to enable call trace");
714 sink.example(L">> LOG CATEGORY calltrace 0", L"to disable call trace");
717 std::wstring log_category_command(command_context& ctx)
719 log::set_log_category(ctx.parameters.at(0), ctx.parameters.at(1) == L"1");
721 return L"202 LOG OK\r\n";
724 void set_describer(core::help_sink& sink, const core::help_repository& repo)
726 sink.short_description(L"Change the value of a channel variable.");
727 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
728 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
730 ->item(L"MODE", L"Changes the video format of the channel.")
731 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
732 sink.para()->text(L"Examples:");
733 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
734 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
737 std::wstring set_command(command_context& ctx)
739 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
740 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
744 auto format_desc = core::video_format_desc(value);
745 if (format_desc.format != core::video_format::invalid)
747 ctx.channel.channel->video_format_desc(format_desc);
748 return L"202 SET MODE OK\r\n";
751 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video mode"));
753 else if (name == L"CHANNEL_LAYOUT")
755 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
759 ctx.channel.channel->audio_channel_layout(*channel_layout);
760 return L"202 SET CHANNEL_LAYOUT OK\r\n";
763 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid audio channel layout"));
766 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid channel variable"));
769 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
771 sink.short_description(L"Store a dataset.");
772 sink.syntax(L"DATA STORE [name:string] [data:string]");
773 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
774 sink.para()->text(L"Directories will be created if they do not exist.");
775 sink.para()->text(L"Examples:");
776 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
777 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
780 std::wstring data_store_command(command_context& ctx)
782 std::wstring filename = env::data_folder();
783 filename.append(ctx.parameters[0]);
784 filename.append(L".ftd");
786 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
787 auto found_data_path = find_case_insensitive(data_path);
790 data_path = *found_data_path;
792 if (!boost::filesystem::exists(data_path))
793 boost::filesystem::create_directories(data_path);
795 auto found_filename = find_case_insensitive(filename);
798 filename = *found_filename; // Overwrite case insensitive.
800 boost::filesystem::wofstream datafile(filename);
802 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
804 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
805 datafile << ctx.parameters[1] << std::flush;
808 return L"202 DATA STORE OK\r\n";
811 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
813 sink.short_description(L"Retrieve a dataset.");
814 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
815 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
816 sink.para()->text(L"Examples:");
817 sink.example(L">> DATA RETRIEVE my_data");
818 sink.example(L">> DATA RETRIEVE Folder1/my_data");
821 std::wstring data_retrieve_command(command_context& ctx)
823 std::wstring filename = env::data_folder();
824 filename.append(ctx.parameters[0]);
825 filename.append(L".ftd");
827 std::wstring file_contents;
829 auto found_file = find_case_insensitive(filename);
832 file_contents = read_file(boost::filesystem::path(*found_file));
834 if (file_contents.empty())
835 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
837 std::wstringstream reply;
838 reply << L"201 DATA RETRIEVE OK\r\n";
840 std::wstringstream file_contents_stream(file_contents);
843 bool firstLine = true;
844 while (std::getline(file_contents_stream, line))
858 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
860 sink.short_description(L"List stored datasets.");
861 sink.syntax(L"DATA LIST");
862 sink.para()->text(L"Returns a list of all stored datasets.");
865 std::wstring data_list_command(command_context& ctx)
867 std::wstringstream replyString;
868 replyString << L"200 DATA LIST OK\r\n";
870 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
872 if (boost::filesystem::is_regular_file(itr->path()))
874 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
877 auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
878 auto str = relativePath.generic_wstring();
880 if (str[0] == L'\\' || str[0] == L'/')
881 str = std::wstring(str.begin() + 1, str.end());
883 replyString << str << L"\r\n";
887 replyString << L"\r\n";
889 return boost::to_upper_copy(replyString.str());
892 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
894 sink.short_description(L"Remove a stored dataset.");
895 sink.syntax(L"DATA REMOVE [name:string]");
896 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
897 sink.para()->text(L"Examples:");
898 sink.example(L">> DATA REMOVE my_data");
899 sink.example(L">> DATA REMOVE Folder1/my_data");
902 std::wstring data_remove_command(command_context& ctx)
904 std::wstring filename = env::data_folder();
905 filename.append(ctx.parameters[0]);
906 filename.append(L".ftd");
908 if (!boost::filesystem::exists(filename))
909 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
911 if (!boost::filesystem::remove(filename))
912 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
914 return L"202 DATA REMOVE OK\r\n";
917 // Template Graphics Commands
919 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
921 sink.short_description(L"Prepare a template for displaying.");
922 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
924 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
925 ->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.");
926 sink.para()->text(L"Examples:");
927 sink.example(L"CG 1 ADD 10 svtnews/info 1");
930 std::wstring cg_add_command(command_context& ctx)
932 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
934 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
935 std::wstring label; //_parameters[2]
936 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
937 unsigned int dataIndex = 3;
939 if (ctx.parameters.at(2).length() > 1)
941 label = ctx.parameters.at(2);
944 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
945 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
948 { //read play-on-load-flag
949 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
952 const wchar_t* pDataString = 0;
953 std::wstring dataFromFile;
954 if (ctx.parameters.size() > dataIndex)
956 const std::wstring& dataString = ctx.parameters.at(dataIndex);
958 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
959 pDataString = dataString.c_str();
962 //The data is not an XML-string, it must be a filename
963 std::wstring filename = env::data_folder();
964 filename.append(dataString);
965 filename.append(L".ftd");
967 auto found_file = find_case_insensitive(filename);
971 dataFromFile = read_file(boost::filesystem::path(*found_file));
972 pDataString = dataFromFile.c_str();
977 auto filename = ctx.parameters.at(1);
978 auto proxy = ctx.cg_registry->get_or_create_proxy(
979 spl::make_shared_ptr(ctx.channel.channel),
980 get_producer_dependencies(ctx.channel.channel, ctx),
981 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
984 if (proxy == core::cg_proxy::empty())
985 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
987 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
989 return L"202 CG OK\r\n";
992 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
994 sink.short_description(L"Play and display a template.");
995 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
996 sink.para()->text(L"Plays and displays the template in the specified layer.");
997 sink.para()->text(L"Examples:");
998 sink.example(L"CG 1 PLAY 0");
1001 std::wstring cg_play_command(command_context& ctx)
1003 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1004 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1006 return L"202 CG OK\r\n";
1009 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1011 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1013 if (proxy == cg_proxy::empty())
1014 CASPAR_THROW_EXCEPTION(expected_user_error() << msg_info(L"No CG proxy running on layer"));
1019 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1021 sink.short_description(L"Stop and remove a template.");
1022 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1024 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1025 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1026 sink.para()->text(L"Examples:");
1027 sink.example(L"CG 1 STOP 0");
1030 std::wstring cg_stop_command(command_context& ctx)
1032 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1033 get_expected_cg_proxy(ctx)->stop(layer, 0);
1035 return L"202 CG OK\r\n";
1038 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1040 sink.short_description(LR"(Trigger a "continue" in a template.)");
1041 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1043 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1044 ->text(L"This is used to control animations that has multiple discreet steps.");
1045 sink.para()->text(L"Examples:");
1046 sink.example(L"CG 1 NEXT 0");
1049 std::wstring cg_next_command(command_context& ctx)
1051 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1052 get_expected_cg_proxy(ctx)->next(layer);
1054 return L"202 CG OK\r\n";
1057 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1059 sink.short_description(L"Remove a template.");
1060 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1061 sink.para()->text(L"Removes the template from the specified layer.");
1062 sink.para()->text(L"Examples:");
1063 sink.example(L"CG 1 REMOVE 0");
1066 std::wstring cg_remove_command(command_context& ctx)
1068 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1069 get_expected_cg_proxy(ctx)->remove(layer);
1071 return L"202 CG OK\r\n";
1074 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1076 sink.short_description(L"Remove all templates on a video layer.");
1077 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1078 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1079 sink.para()->text(L"Examples:");
1080 sink.example(L"CG 1 CLEAR");
1083 std::wstring cg_clear_command(command_context& ctx)
1085 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1087 return L"202 CG OK\r\n";
1090 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1092 sink.short_description(L"Update a template with new data.");
1093 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1094 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.");
1097 std::wstring cg_update_command(command_context& ctx)
1099 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1101 std::wstring dataString = ctx.parameters.at(1);
1102 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1104 //The data is not XML or Json, it must be a filename
1105 std::wstring filename = env::data_folder();
1106 filename.append(dataString);
1107 filename.append(L".ftd");
1109 dataString = read_file(boost::filesystem::path(filename));
1112 get_expected_cg_proxy(ctx)->update(layer, dataString);
1114 return L"202 CG OK\r\n";
1117 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1119 sink.short_description(L"Invoke a method/label on a template.");
1120 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1121 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1122 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1125 std::wstring cg_invoke_command(command_context& ctx)
1127 std::wstringstream replyString;
1128 replyString << L"201 CG OK\r\n";
1129 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1130 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1131 replyString << result << L"\r\n";
1133 return replyString.str();
1136 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1138 sink.short_description(L"Get information about a running template or the template host.");
1139 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1140 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1141 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1144 std::wstring cg_info_command(command_context& ctx)
1146 std::wstringstream replyString;
1147 replyString << L"201 CG OK\r\n";
1149 if (ctx.parameters.empty())
1151 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1152 replyString << info << L"\r\n";
1156 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1157 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1159 replyString << desc << L"\r\n";
1162 return replyString.str();
1167 core::frame_transform get_current_transform(command_context& ctx)
1169 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1172 template<typename Func>
1173 std::wstring reply_value(command_context& ctx, const Func& extractor)
1175 auto value = extractor(get_current_transform(ctx));
1177 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1180 class transforms_applier
1182 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1184 std::vector<stage::transform_tuple_t> transforms_;
1185 command_context& ctx_;
1188 transforms_applier(command_context& ctx)
1191 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1194 ctx.parameters.pop_back();
1197 void add(stage::transform_tuple_t&& transform)
1199 transforms_.push_back(std::move(transform));
1202 void commit_deferred()
1204 auto& transforms = deferred_transforms_[ctx_.channel_index];
1205 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1213 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1214 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1217 ctx_.channel.channel->stage().apply_transforms(transforms_);
1220 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1222 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1224 sink.short_description(L"Let a layer act as alpha for the one obove.");
1225 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1227 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1228 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1229 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1230 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1231 ->text(L"instead it will be used as the key for the layer above.");
1232 sink.para()->text(L"Examples:");
1233 sink.example(L">> MIXER 1-0 KEYER 1");
1235 L">> MIXER 1-0 KEYER\n"
1236 L"<< 201 MIXER OK\n"
1237 L"<< 1", L"to retrieve the current state");
1240 std::wstring mixer_keyer_command(command_context& ctx)
1242 if (ctx.parameters.empty())
1243 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1245 transforms_applier transforms(ctx);
1246 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1247 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1249 transform.image_transform.is_key = value;
1254 return L"202 MIXER OK\r\n";
1257 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1259 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1261 sink.short_description(L"Enable chroma keying on a layer.");
1262 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1264 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1265 sink.para()->text(L"Examples:");
1266 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1267 sink.example(L">> MIXER 1-1 CHROMA none");
1269 L">> MIXER 1-1 BLEND\n"
1270 L"<< 201 MIXER OK\n"
1271 L"<< SCREEN", L"for getting the current blend mode");
1274 std::wstring mixer_chroma_command(command_context& ctx)
1276 if (ctx.parameters.empty())
1278 auto chroma = get_current_transform(ctx).image_transform.chroma;
1279 return L"201 MIXER OK\r\n"
1280 + core::get_chroma_mode(chroma.key) + L" "
1281 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1282 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1283 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1286 transforms_applier transforms(ctx);
1287 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1288 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1290 core::chroma chroma;
1291 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1293 if (chroma.key != core::chroma::type::none)
1295 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1296 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1297 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1300 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1302 transform.image_transform.chroma = chroma;
1304 }, duration, tween));
1307 return L"202 MIXER OK\r\n";
1310 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1312 sink.short_description(L"Set the blend mode for a layer.");
1313 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1315 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1316 ->text(L"If no argument is given the current blend mode is returned.");
1318 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1319 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1320 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1321 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1322 sink.para()->text(L"Examples:");
1323 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1325 L">> MIXER 1-1 BLEND\n"
1326 L"<< 201 MIXER OK\n"
1327 L"<< SCREEN", L"for getting the current blend mode");
1328 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1331 std::wstring mixer_blend_command(command_context& ctx)
1333 if (ctx.parameters.empty())
1334 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1336 transforms_applier transforms(ctx);
1337 auto value = get_blend_mode(ctx.parameters.at(0));
1338 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1340 transform.image_transform.blend_mode = value;
1345 return L"202 MIXER OK\r\n";
1348 template<typename Getter, typename Setter>
1349 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1351 if (ctx.parameters.empty())
1352 return reply_value(ctx, getter);
1354 transforms_applier transforms(ctx);
1355 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1356 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1357 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1359 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1361 setter(transform, value);
1363 }, duration, tween));
1366 return L"202 MIXER OK\r\n";
1369 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1371 sink.short_description(L"Change the opacity of a layer.");
1372 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1373 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1374 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1375 sink.para()->text(L"Examples:");
1376 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1378 L">> MIXER 1-0 OPACITY\n"
1379 L"<< 201 MIXER OK\n"
1380 L"<< 0.5", L"to retrieve the current opacity");
1383 std::wstring mixer_opacity_command(command_context& ctx)
1385 return single_double_animatable_mixer_command(
1387 [](const frame_transform& t) { return t.image_transform.opacity; },
1388 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1391 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1393 sink.short_description(L"Change the brightness of a layer.");
1394 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1395 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1396 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1397 sink.para()->text(L"Examples:");
1398 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1400 L">> MIXER 1-0 BRIGHTNESS\n"
1401 L"<< 201 MIXER OK\n"
1402 L"0.5", L"to retrieve the current brightness");
1405 std::wstring mixer_brightness_command(command_context& ctx)
1407 return single_double_animatable_mixer_command(
1409 [](const frame_transform& t) { return t.image_transform.brightness; },
1410 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1413 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1415 sink.short_description(L"Change the saturation of a layer.");
1416 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1417 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1418 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1419 sink.para()->text(L"Examples:");
1420 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1422 L">> MIXER 1-0 SATURATION\n"
1423 L"<< 201 MIXER OK\n"
1424 L"<< 0.5", L"to retrieve the current saturation");
1427 std::wstring mixer_saturation_command(command_context& ctx)
1429 return single_double_animatable_mixer_command(
1431 [](const frame_transform& t) { return t.image_transform.saturation; },
1432 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1435 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1437 sink.short_description(L"Change the contrast of a layer.");
1438 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1439 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1440 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1441 sink.para()->text(L"Examples:");
1442 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1444 L">> MIXER 1-0 CONTRAST\n"
1445 L"<< 201 MIXER OK\n"
1446 L"<< 0.5", L"to retrieve the current contrast");
1449 std::wstring mixer_contrast_command(command_context& ctx)
1451 return single_double_animatable_mixer_command(
1453 [](const frame_transform& t) { return t.image_transform.contrast; },
1454 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1457 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1459 sink.short_description(L"Adjust the video levels of a layer.");
1460 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);
1462 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1464 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1465 ->item(L"gamma", L"Adjusts the gamma of the image.")
1466 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1467 sink.para()->text(L"Examples:");
1468 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");
1469 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");
1470 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1472 L">> MIXER 1-10 LEVELS\n"
1473 L"<< 201 MIXER OK\n"
1474 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1477 std::wstring mixer_levels_command(command_context& ctx)
1479 if (ctx.parameters.empty())
1481 auto levels = get_current_transform(ctx).image_transform.levels;
1482 return L"201 MIXER OK\r\n"
1483 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1484 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1485 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1486 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1487 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1490 transforms_applier transforms(ctx);
1492 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1493 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1494 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1495 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1496 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1497 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1498 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1500 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1502 transform.image_transform.levels = value;
1504 }, duration, tween));
1507 return L"202 MIXER OK\r\n";
1510 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1512 sink.short_description(L"Change the fill position and scale of a layer.");
1513 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1515 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1516 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1517 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1518 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1520 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1521 ->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.");
1522 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1524 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1525 ->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, ")
1526 ->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");
1528 ->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.")
1529 ->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.")
1530 ->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.")
1531 ->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.");
1532 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1533 sink.para()->text(L"Examples:");
1534 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1536 L">> MIXER 1-0 FILL\n"
1537 L"<< 201 MIXER OK\n"
1538 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1541 std::wstring mixer_fill_command(command_context& ctx)
1543 if (ctx.parameters.empty())
1545 auto transform = get_current_transform(ctx).image_transform;
1546 auto translation = transform.fill_translation;
1547 auto scale = transform.fill_scale;
1548 return L"201 MIXER OK\r\n"
1549 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1550 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1551 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1552 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1555 transforms_applier transforms(ctx);
1556 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1557 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1558 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1559 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1560 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1561 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1563 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1565 transform.image_transform.fill_translation[0] = x;
1566 transform.image_transform.fill_translation[1] = y;
1567 transform.image_transform.fill_scale[0] = x_s;
1568 transform.image_transform.fill_scale[1] = y_s;
1570 }, duration, tween));
1573 return L"202 MIXER OK\r\n";
1576 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1578 sink.short_description(L"Change the clipping viewport of a layer.");
1579 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1581 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1582 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1583 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1585 ->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.")
1586 ->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.")
1587 ->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.")
1588 ->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.");
1589 sink.para()->text(L"Examples:");
1590 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1592 L">> MIXER 1-0 CLIP\n"
1593 L"<< 201 MIXER OK\n"
1594 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1597 std::wstring mixer_clip_command(command_context& ctx)
1599 if (ctx.parameters.empty())
1601 auto transform = get_current_transform(ctx).image_transform;
1602 auto translation = transform.clip_translation;
1603 auto scale = transform.clip_scale;
1605 return L"201 MIXER OK\r\n"
1606 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1607 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1608 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1609 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1612 transforms_applier transforms(ctx);
1613 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1614 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1615 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1616 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1617 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1618 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1620 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1622 transform.image_transform.clip_translation[0] = x;
1623 transform.image_transform.clip_translation[1] = y;
1624 transform.image_transform.clip_scale[0] = x_s;
1625 transform.image_transform.clip_scale[1] = y_s;
1627 }, duration, tween));
1630 return L"202 MIXER OK\r\n";
1633 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1635 sink.short_description(L"Change the anchor point of a layer.");
1636 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1637 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1639 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1640 ->text(L" will be done from.");
1642 ->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.")
1643 ->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.");
1644 sink.para()->text(L"Examples:");
1645 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1647 L">> MIXER 1-10 ANCHOR\n"
1648 L"<< 201 MIXER OK\n"
1649 L"<< 0.5 0.6", L"gets the anchor point");
1652 std::wstring mixer_anchor_command(command_context& ctx)
1654 if (ctx.parameters.empty())
1656 auto transform = get_current_transform(ctx).image_transform;
1657 auto anchor = transform.anchor;
1658 return L"201 MIXER OK\r\n"
1659 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1660 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1663 transforms_applier transforms(ctx);
1664 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1665 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1666 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1667 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1669 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1671 transform.image_transform.anchor[0] = x;
1672 transform.image_transform.anchor[1] = y;
1674 }, duration, tween));
1677 return L"202 MIXER OK\r\n";
1680 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1682 sink.short_description(L"Crop a layer.");
1683 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);
1685 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1686 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1687 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1689 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1690 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1691 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1692 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1693 sink.para()->text(L"Examples:");
1694 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");
1696 L">> MIXER 1-0 CROP\n"
1697 L"<< 201 MIXER OK\n"
1698 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1701 std::wstring mixer_crop_command(command_context& ctx)
1703 if (ctx.parameters.empty())
1705 auto crop = get_current_transform(ctx).image_transform.crop;
1706 return L"201 MIXER OK\r\n"
1707 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1708 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1709 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1710 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1713 transforms_applier transforms(ctx);
1714 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1715 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1716 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1717 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1718 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1719 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1721 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1723 transform.image_transform.crop.ul[0] = ul_x;
1724 transform.image_transform.crop.ul[1] = ul_y;
1725 transform.image_transform.crop.lr[0] = lr_x;
1726 transform.image_transform.crop.lr[1] = lr_y;
1728 }, duration, tween));
1731 return L"202 MIXER OK\r\n";
1734 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1736 sink.short_description(L"Rotate a layer.");
1737 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1739 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1740 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1741 sink.para()->text(L"Examples:");
1742 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1744 L">> MIXER 1-0 ROTATION\n"
1745 L"<< 201 MIXER OK\n"
1746 L"<< 45", L"to retrieve the current angle");
1749 std::wstring mixer_rotation_command(command_context& ctx)
1751 static const double PI = 3.141592653589793;
1753 return single_double_animatable_mixer_command(
1755 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1756 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1759 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1761 sink.short_description(L"Adjust the perspective transform of a layer.");
1762 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);
1764 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1766 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1767 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1768 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1769 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1770 sink.para()->text(L"Examples:");
1771 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1773 L">> MIXER 1-10 PERSPECTIVE\n"
1774 L"<< 201 MIXER OK\n"
1775 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1778 std::wstring mixer_perspective_command(command_context& ctx)
1780 if (ctx.parameters.empty())
1782 auto perspective = get_current_transform(ctx).image_transform.perspective;
1785 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1786 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1787 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1788 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1789 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1790 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1791 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1792 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1795 transforms_applier transforms(ctx);
1796 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1797 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1798 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1799 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1800 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1801 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1802 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1803 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1804 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1805 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1807 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1809 transform.image_transform.perspective.ul[0] = ul_x;
1810 transform.image_transform.perspective.ul[1] = ul_y;
1811 transform.image_transform.perspective.ur[0] = ur_x;
1812 transform.image_transform.perspective.ur[1] = ur_y;
1813 transform.image_transform.perspective.lr[0] = lr_x;
1814 transform.image_transform.perspective.lr[1] = lr_y;
1815 transform.image_transform.perspective.ll[0] = ll_x;
1816 transform.image_transform.perspective.ll[1] = ll_y;
1818 }, duration, tween));
1821 return L"202 MIXER OK\r\n";
1824 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1826 sink.short_description(L"Enable or disable mipmapping for a layer.");
1827 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1829 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1830 ->text(L"If no argument is given the current state is returned.");
1831 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1832 sink.para()->text(L"Examples:");
1833 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1835 L">> MIXER 1-10 MIPMAP\n"
1836 L"<< 201 MIXER OK\n"
1837 L"<< 1", L"for getting the current state");
1840 std::wstring mixer_mipmap_command(command_context& ctx)
1842 if (ctx.parameters.empty())
1843 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1845 transforms_applier transforms(ctx);
1846 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1847 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1849 transform.image_transform.use_mipmap = value;
1854 return L"202 MIXER OK\r\n";
1857 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1859 sink.short_description(L"Change the volume of a layer.");
1860 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1861 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1862 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1863 sink.para()->text(L"Examples:");
1864 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1865 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1867 L">> MIXER 1-0 VOLUME\n"
1868 L"<< 201 MIXER OK\n"
1869 L"<< 0.8", L"to retrieve the current volume");
1872 std::wstring mixer_volume_command(command_context& ctx)
1874 return single_double_animatable_mixer_command(
1876 [](const frame_transform& t) { return t.audio_transform.volume; },
1877 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1880 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1882 sink.short_description(L"Change the volume of an entire channel.");
1883 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1884 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1885 sink.para()->text(L"Examples:");
1886 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1887 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1888 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1891 std::wstring mixer_mastervolume_command(command_context& ctx)
1893 if (ctx.parameters.empty())
1895 auto volume = ctx.channel.channel->mixer().get_master_volume();
1896 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1899 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1900 ctx.channel.channel->mixer().set_master_volume(master_volume);
1902 return L"202 MIXER OK\r\n";
1905 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1907 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1908 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1909 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1910 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1911 sink.para()->text(L"Examples:");
1912 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1913 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1915 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1916 L"<< 201 MIXER OK\n"
1920 std::wstring mixer_straight_alpha_command(command_context& ctx)
1922 if (ctx.parameters.empty())
1924 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1925 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1928 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1929 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1931 return L"202 MIXER OK\r\n";
1934 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1936 sink.short_description(L"Create a grid of video layers.");
1937 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1939 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1940 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1941 sink.para()->text(L"Examples:");
1942 sink.example(L">> MIXER 1 GRID 2");
1945 std::wstring mixer_grid_command(command_context& ctx)
1947 transforms_applier transforms(ctx);
1948 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1949 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1950 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1951 double delta = 1.0 / static_cast<double>(n);
1952 for (int x = 0; x < n; ++x)
1954 for (int y = 0; y < n; ++y)
1956 int index = x + y*n + 1;
1957 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1959 transform.image_transform.fill_translation[0] = x*delta;
1960 transform.image_transform.fill_translation[1] = y*delta;
1961 transform.image_transform.fill_scale[0] = delta;
1962 transform.image_transform.fill_scale[1] = delta;
1963 transform.image_transform.clip_translation[0] = x*delta;
1964 transform.image_transform.clip_translation[1] = y*delta;
1965 transform.image_transform.clip_scale[0] = delta;
1966 transform.image_transform.clip_scale[1] = delta;
1968 }, duration, tween));
1973 return L"202 MIXER OK\r\n";
1976 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1978 sink.short_description(L"Commit all deferred mixer transforms.");
1979 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1980 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1981 sink.para()->text(L"Examples:");
1983 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1984 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1985 L">> MIXER 1 COMMIT");
1988 std::wstring mixer_commit_command(command_context& ctx)
1990 transforms_applier transforms(ctx);
1991 transforms.commit_deferred();
1993 return L"202 MIXER OK\r\n";
1996 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1998 sink.short_description(L"Clear all transformations on a channel or layer.");
1999 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
2000 sink.para()->text(L"Clears all transformations on a channel or layer.");
2001 sink.para()->text(L"Examples:");
2002 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
2003 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
2006 std::wstring mixer_clear_command(command_context& ctx)
2008 int layer = ctx.layer_id;
2011 ctx.channel.channel->stage().clear_transforms();
2013 ctx.channel.channel->stage().clear_transforms(layer);
2015 return L"202 MIXER OK\r\n";
2018 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2020 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2021 sink.syntax(L"CHANNEL_GRID");
2022 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2024 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2025 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2028 std::wstring channel_grid_command(command_context& ctx)
2031 auto self = ctx.channels.back();
2033 core::diagnostics::scoped_call_context save;
2034 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2036 std::vector<std::wstring> params;
2037 params.push_back(L"SCREEN");
2038 params.push_back(L"0");
2039 params.push_back(L"NAME");
2040 params.push_back(L"Channel Grid Window");
2041 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
2043 self.channel->output().add(screen);
2045 for (auto& channel : ctx.channels)
2047 if (channel.channel != self.channel)
2049 core::diagnostics::call_context::for_thread().layer = index;
2050 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2051 self.channel->stage().load(index, producer, false);
2052 self.channel->stage().play(index);
2057 auto num_channels = ctx.channels.size() - 1;
2058 int square_side_length = std::ceil(std::sqrt(num_channels));
2060 ctx.channel_index = self.channel->index();
2062 ctx.parameters.clear();
2063 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2064 mixer_grid_command(ctx);
2066 return L"202 CHANNEL_GRID OK\r\n";
2069 // Thumbnail Commands
2071 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2073 sink.short_description(L"List all thumbnails.");
2074 sink.syntax(L"THUMBNAIL LIST");
2075 sink.para()->text(L"Lists all thumbnails.");
2076 sink.para()->text(L"Examples:");
2078 L">> THUMBNAIL LIST\n"
2079 L"<< 200 THUMBNAIL LIST OK\n"
2080 L"<< \"AMB\" 20130301T124409 1149\n"
2081 L"<< \"foo/bar\" 20130523T234001 244");
2084 std::wstring thumbnail_list_command(command_context& ctx)
2086 std::wstringstream replyString;
2087 replyString << L"200 THUMBNAIL LIST OK\r\n";
2089 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2091 if (boost::filesystem::is_regular_file(itr->path()))
2093 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2096 auto relativePath = get_relative_without_extension(itr->path(), env::thumbnails_folder());
2097 auto str = relativePath.generic_wstring();
2099 if (str[0] == '\\' || str[0] == '/')
2100 str = std::wstring(str.begin() + 1, str.end());
2102 auto mtime = boost::filesystem::last_write_time(itr->path());
2103 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2104 auto file_size = boost::filesystem::file_size(itr->path());
2106 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2110 replyString << L"\r\n";
2112 return boost::to_upper_copy(replyString.str());
2115 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2117 sink.short_description(L"Retrieve a thumbnail.");
2118 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2119 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2120 sink.para()->text(L"Examples:");
2122 L">> THUMBNAIL RETRIEVE foo/bar\n"
2123 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2124 L"<< ...base64 data...");
2127 std::wstring thumbnail_retrieve_command(command_context& ctx)
2129 std::wstring filename = env::thumbnails_folder();
2130 filename.append(ctx.parameters.at(0));
2131 filename.append(L".png");
2133 std::wstring file_contents;
2135 auto found_file = find_case_insensitive(filename);
2138 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2140 if (file_contents.empty())
2141 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2143 std::wstringstream reply;
2145 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2146 reply << file_contents;
2151 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2153 sink.short_description(L"Regenerate a thumbnail.");
2154 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2155 sink.para()->text(L"Regenerates a thumbnail.");
2158 std::wstring thumbnail_generate_command(command_context& ctx)
2162 ctx.thumb_gen->generate(ctx.parameters.at(0));
2163 return L"202 THUMBNAIL GENERATE OK\r\n";
2166 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2169 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2171 sink.short_description(L"Regenerate all thumbnails.");
2172 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2173 sink.para()->text(L"Regenerates all thumbnails.");
2176 std::wstring thumbnail_generateall_command(command_context& ctx)
2180 ctx.thumb_gen->generate_all();
2181 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2184 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2189 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2191 sink.short_description(L"Get information about a media file.");
2192 sink.syntax(L"CINF [filename:string]");
2193 sink.para()->text(L"Returns information about a media file.");
2196 std::wstring cinf_command(command_context& ctx)
2199 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2201 auto path = itr->path();
2202 auto file = path.replace_extension(L"").filename().wstring();
2203 if (boost::iequals(file, ctx.parameters.at(0)))
2204 info += MediaInfo(itr->path(), ctx.media_info_repo);
2208 CASPAR_THROW_EXCEPTION(file_not_found());
2210 std::wstringstream replyString;
2211 replyString << L"200 CINF OK\r\n";
2212 replyString << info << "\r\n";
2214 return replyString.str();
2217 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2219 sink.short_description(L"List all media files.");
2220 sink.syntax(L"CLS");
2222 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2223 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2226 std::wstring cls_command(command_context& ctx)
2228 std::wstringstream replyString;
2229 replyString << L"200 CLS OK\r\n";
2230 replyString << ListMedia(ctx.media_info_repo);
2231 replyString << L"\r\n";
2232 return boost::to_upper_copy(replyString.str());
2235 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2237 sink.short_description(L"List all fonts.");
2238 sink.syntax(L"FLS");
2240 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2241 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2242 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2245 std::wstring fls_command(command_context& ctx)
2247 std::wstringstream replyString;
2248 replyString << L"200 FLS OK\r\n";
2250 for (auto& font : core::text::list_fonts())
2251 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2253 replyString << L"\r\n";
2255 return replyString.str();
2258 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2260 sink.short_description(L"List all templates.");
2261 sink.syntax(L"TLS");
2263 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2264 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2267 std::wstring tls_command(command_context& ctx)
2269 std::wstringstream replyString;
2270 replyString << L"200 TLS OK\r\n";
2272 replyString << ListTemplates(ctx.cg_registry);
2273 replyString << L"\r\n";
2275 return replyString.str();
2278 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2280 sink.short_description(L"Get version information.");
2281 sink.syntax(L"VERSION {[component:string]}");
2282 sink.para()->text(L"Returns the version of specified component.");
2283 sink.para()->text(L"Examples:");
2286 L"<< 201 VERSION OK\n"
2287 L"<< 2.1.0.f207a33 STABLE");
2289 L">> VERSION SERVER\n"
2290 L"<< 201 VERSION OK\n"
2291 L"<< 2.1.0.f207a33 STABLE");
2293 L">> VERSION FLASH\n"
2294 L"<< 201 VERSION OK\n"
2298 std::wstring version_command(command_context& ctx)
2300 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2302 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2304 return L"201 VERSION OK\r\n" + version + L"\r\n";
2307 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2310 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2312 sink.short_description(L"Get a list of the available channels.");
2313 sink.syntax(L"INFO");
2314 sink.para()->text(L"Retrieves a list of the available channels.");
2318 L"<< 1 720p5000 PLAYING\n"
2319 L"<< 2 PAL PLAYING");
2322 std::wstring info_command(command_context& ctx)
2324 std::wstringstream replyString;
2325 // This is needed for backwards compatibility with old clients
2326 replyString << L"200 INFO OK\r\n";
2327 for (size_t n = 0; n < ctx.channels.size(); ++n)
2328 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2329 replyString << L"\r\n";
2330 return replyString.str();
2333 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2335 std::wstringstream replyString;
2337 if (command.empty())
2338 replyString << L"201 INFO OK\r\n";
2340 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2342 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2343 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2344 replyString << L"\r\n";
2345 return replyString.str();
2348 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2350 sink.short_description(L"Get information about a channel or a layer.");
2351 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2352 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2353 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2356 std::wstring info_channel_command(command_context& ctx)
2358 boost::property_tree::wptree info;
2359 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2361 if (layer == std::numeric_limits<int>::min())
2363 info.add_child(L"channel", ctx.channel.channel->info())
2364 .add(L"index", ctx.channel_index);
2368 if (ctx.parameters.size() >= 1)
2370 if (boost::iequals(ctx.parameters.at(0), L"B"))
2371 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2373 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2377 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2381 return create_info_xml_reply(info);
2384 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2386 sink.short_description(L"Get information about a template.");
2387 sink.syntax(L"INFO TEMPLATE [template:string]");
2388 sink.para()->text(L"Gets information about the specified template.");
2391 std::wstring info_template_command(command_context& ctx)
2393 auto filename = ctx.parameters.at(0);
2395 std::wstringstream str;
2396 str << u16(ctx.cg_registry->read_meta_info(filename));
2397 boost::property_tree::wptree info;
2398 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2400 return create_info_xml_reply(info, L"TEMPLATE");
2403 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2405 sink.short_description(L"Get the contents of the configuration used.");
2406 sink.syntax(L"INFO CONFIG");
2407 sink.para()->text(L"Gets the contents of the configuration used.");
2410 std::wstring info_config_command(command_context& ctx)
2412 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2415 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2417 sink.short_description(L"Get information about the paths used.");
2418 sink.syntax(L"INFO PATHS");
2419 sink.para()->text(L"Gets information about the paths used.");
2422 std::wstring info_paths_command(command_context& ctx)
2424 boost::property_tree::wptree info;
2425 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2426 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2428 return create_info_xml_reply(info, L"PATHS");
2431 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2433 sink.short_description(L"Get system information.");
2434 sink.syntax(L"INFO SYSTEM");
2435 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2438 std::wstring info_system_command(command_context& ctx)
2440 boost::property_tree::wptree info;
2442 info.add(L"system.name", caspar::system_product_name());
2443 info.add(L"system.os.description", caspar::os_description());
2444 info.add(L"system.cpu", caspar::cpu_info());
2446 ctx.system_info_repo->fill_information(info);
2448 return create_info_xml_reply(info, L"SYSTEM");
2451 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2453 sink.short_description(L"Get detailed information about all channels.");
2454 sink.syntax(L"INFO SERVER");
2455 sink.para()->text(L"Gets detailed information about all channels.");
2458 std::wstring info_server_command(command_context& ctx)
2460 boost::property_tree::wptree info;
2463 for (auto& channel : ctx.channels)
2464 info.add_child(L"channels.channel", channel.channel->info())
2465 .add(L"index", ++index);
2467 return create_info_xml_reply(info, L"SERVER");
2470 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2472 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2473 sink.syntax(L"INFO QUEUES");
2474 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2477 std::wstring info_queues_command(command_context& ctx)
2479 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2482 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2484 sink.short_description(L"Lists all known threads in the server.");
2485 sink.syntax(L"INFO THREADS");
2486 sink.para()->text(L"Lists all known threads in the server.");
2489 std::wstring info_threads_command(command_context& ctx)
2491 std::wstringstream replyString;
2492 replyString << L"200 INFO THREADS OK\r\n";
2494 for (auto& thread : get_thread_infos())
2496 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2499 replyString << L"\r\n";
2500 return replyString.str();
2503 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2505 sink.short_description(L"Get the current delay on a channel or a layer.");
2506 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2507 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2510 std::wstring info_delay_command(command_context& ctx)
2512 boost::property_tree::wptree info;
2513 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2515 if (layer == std::numeric_limits<int>::min())
2516 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2518 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2519 .add(L"index", layer);
2521 return create_info_xml_reply(info, L"DELAY");
2524 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2526 sink.short_description(L"Open the diagnostics window.");
2527 sink.syntax(L"DIAG");
2528 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2531 std::wstring diag_command(command_context& ctx)
2533 core::diagnostics::osd::show_graphs(true);
2535 return L"202 DIAG OK\r\n";
2538 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2540 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2541 sink.syntax(L"GL INFO");
2542 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2545 std::wstring gl_info_command(command_context& ctx)
2547 auto device = ctx.ogl_device;
2550 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2552 std::wstringstream result;
2553 result << L"201 GL INFO OK\r\n";
2555 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2556 auto info = device->info();
2557 boost::property_tree::write_xml(result, info, w);
2560 return result.str();
2563 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2565 sink.short_description(L"Release pooled OpenGL resources.");
2566 sink.syntax(L"GL GC");
2567 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2570 std::wstring gl_gc_command(command_context& ctx)
2572 auto device = ctx.ogl_device;
2575 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2577 device->gc().wait();
2579 return L"202 GL GC OK\r\n";
2582 static const int WIDTH = 80;
2584 struct max_width_sink : public core::help_sink
2586 std::size_t max_width = 0;
2588 void begin_item(const std::wstring& name) override
2590 max_width = std::max(name.length(), max_width);
2594 struct short_description_sink : public core::help_sink
2597 std::wstringstream& out;
2599 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2601 void begin_item(const std::wstring& name) override
2603 out << std::left << std::setw(width + 1) << name;
2606 void short_description(const std::wstring& short_description) override
2608 out << short_description << L"\r\n";
2612 struct simple_paragraph_builder : core::paragraph_builder
2614 std::wostringstream out;
2615 std::wstringstream& commit_to;
2617 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2618 ~simple_paragraph_builder()
2620 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2622 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2624 out << std::move(text);
2625 return shared_from_this();
2627 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2628 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2629 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2630 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2633 struct simple_definition_list_builder : core::definition_list_builder
2635 std::wstringstream& out;
2637 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2638 ~simple_definition_list_builder()
2643 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2645 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2646 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2647 return shared_from_this();
2651 struct long_description_sink : public core::help_sink
2653 std::wstringstream& out;
2655 long_description_sink(std::wstringstream& out) : out(out) { }
2657 void syntax(const std::wstring& syntax) override
2659 out << L"Syntax:\n";
2660 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2663 spl::shared_ptr<core::paragraph_builder> para() override
2665 return spl::make_shared<simple_paragraph_builder>(out);
2668 spl::shared_ptr<core::definition_list_builder> definitions() override
2670 return spl::make_shared<simple_definition_list_builder>(out);
2673 void example(const std::wstring& code, const std::wstring& caption) override
2675 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2677 if (!caption.empty())
2678 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2683 void begin_item(const std::wstring& name) override
2685 out << name << L"\n\n";
2689 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2691 std::wstringstream result;
2692 result << L"200 " << help_command << L" OK\r\n";
2693 max_width_sink width;
2694 ctx.help_repo->help(tags, width);
2695 short_description_sink sink(width.max_width, result);
2696 sink.width = width.max_width;
2697 ctx.help_repo->help(tags, sink);
2699 return result.str();
2702 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2704 std::wstringstream result;
2705 result << L"201 " << help_command << L" OK\r\n";
2706 auto joined = boost::join(ctx.parameters, L" ");
2707 long_description_sink sink(result);
2708 ctx.help_repo->help(tags, joined, sink);
2710 return result.str();
2713 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2715 sink.short_description(L"Show online help for AMCP commands.");
2716 sink.syntax(LR"(HELP {[command:string]})");
2717 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2718 sink.example(L">> HELP", L"Shows a list of commands.");
2719 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2722 std::wstring help_command(command_context& ctx)
2724 if (ctx.parameters.size() == 0)
2725 return create_help_list(L"HELP", ctx, { L"AMCP" });
2727 return create_help_details(L"HELP", ctx, { L"AMCP" });
2730 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2732 sink.short_description(L"Show online help for producers.");
2733 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2734 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2735 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2736 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2739 std::wstring help_producer_command(command_context& ctx)
2741 if (ctx.parameters.size() == 0)
2742 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2744 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2747 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2749 sink.short_description(L"Show online help for consumers.");
2750 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2751 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2752 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2753 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2756 std::wstring help_consumer_command(command_context& ctx)
2758 if (ctx.parameters.size() == 0)
2759 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2761 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2764 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2766 sink.short_description(L"Disconnect the session.");
2767 sink.syntax(L"BYE");
2769 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2770 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2773 std::wstring bye_command(command_context& ctx)
2775 ctx.client->disconnect();
2779 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2781 sink.short_description(L"Shutdown the server.");
2782 sink.syntax(L"KILL");
2783 sink.para()->text(L"Shuts the server down.");
2786 std::wstring kill_command(command_context& ctx)
2788 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2789 return L"202 KILL OK\r\n";
2792 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2794 sink.short_description(L"Shutdown the server with restart exit code.");
2795 sink.syntax(L"RESTART");
2797 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2798 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2801 std::wstring restart_command(command_context& ctx)
2803 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2804 return L"202 RESTART OK\r\n";
2807 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2809 sink.short_description(L"Lock or unlock access to a channel.");
2810 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2811 sink.para()->text(L"Allows for exclusive access to a channel.");
2812 sink.para()->text(L"Examples:");
2813 sink.example(L"LOCK 1 ACQUIRE secret");
2814 sink.example(L"LOCK 1 RELEASE");
2815 sink.example(L"LOCK 1 CLEAR");
2818 std::wstring lock_command(command_context& ctx)
2820 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2821 auto lock = ctx.channels.at(channel_index).lock;
2822 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2824 if (command == L"ACQUIRE")
2826 std::wstring lock_phrase = ctx.parameters.at(2);
2828 //TODO: read options
2830 //just lock one channel
2831 if (!lock->try_lock(lock_phrase, ctx.client))
2832 return L"503 LOCK ACQUIRE FAILED\r\n";
2834 return L"202 LOCK ACQUIRE OK\r\n";
2836 else if (command == L"RELEASE")
2838 lock->release_lock(ctx.client);
2839 return L"202 LOCK RELEASE OK\r\n";
2841 else if (command == L"CLEAR")
2843 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2844 std::wstring client_override_phrase;
2846 if (!override_phrase.empty())
2847 client_override_phrase = ctx.parameters.at(2);
2849 //just clear one channel
2850 if (client_override_phrase != override_phrase)
2851 return L"503 LOCK CLEAR FAILED\r\n";
2853 lock->clear_locks();
2855 return L"202 LOCK CLEAR OK\r\n";
2858 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2861 void register_commands(amcp_command_repository& repo)
2863 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2864 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2865 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2866 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2867 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2868 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2869 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2870 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2871 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2872 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2873 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2874 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2875 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2876 repo.register_command( L"Basic Commands", L"LOG CATEGORY", log_category_describer, log_category_command, 2);
2877 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2878 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2880 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2881 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2882 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2883 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2885 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2886 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2887 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2888 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2889 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2890 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2891 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2892 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2893 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2895 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2896 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2897 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2898 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2899 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2900 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2901 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2902 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2903 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2904 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2905 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2906 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2907 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2908 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2909 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2910 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2911 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2912 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2913 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2914 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2915 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2916 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2918 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2919 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2920 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2921 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2923 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2924 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2925 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
2926 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2927 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2928 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2929 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2930 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2931 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2932 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2933 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2934 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2935 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2936 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2937 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2938 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2939 repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
2940 repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
2941 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2942 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2943 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2944 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2945 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2946 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2950 }} //namespace caspar