2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Nicklas P Andersson
22 #include "../StdAfx.h"
25 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
28 #include "AMCPCommandsImpl.h"
30 #include "amcp_command_repository.h"
31 #include "AMCPCommandQueue.h"
33 #include <common/env.h>
35 #include <common/log.h>
36 #include <common/param.h>
37 #include <common/os/system_info.h>
38 #include <common/os/filesystem.h>
39 #include <common/base64.h>
40 #include <common/thread_info.h>
41 #include <common/filesystem.h>
43 #include <core/producer/cg_proxy.h>
44 #include <core/producer/frame_producer.h>
45 #include <core/help/help_repository.h>
46 #include <core/help/help_sink.h>
47 #include <core/help/util.h>
48 #include <core/video_format.h>
49 #include <core/producer/transition/transition_producer.h>
50 #include <core/frame/audio_channel_layout.h>
51 #include <core/frame/frame_transform.h>
52 #include <core/producer/text/text_producer.h>
53 #include <core/producer/stage.h>
54 #include <core/producer/layer.h>
55 #include <core/mixer/mixer.h>
56 #include <core/consumer/output.h>
57 #include <core/thumbnail_generator.h>
58 #include <core/producer/media_info/media_info.h>
59 #include <core/producer/media_info/media_info_repository.h>
60 #include <core/diagnostics/call_context.h>
61 #include <core/diagnostics/osd_graph.h>
62 #include <core/system_info_provider.h>
71 #include <boost/date_time/posix_time/posix_time.hpp>
72 #include <boost/lexical_cast.hpp>
73 #include <boost/algorithm/string.hpp>
74 #include <boost/filesystem.hpp>
75 #include <boost/filesystem/fstream.hpp>
76 #include <boost/regex.hpp>
77 #include <boost/property_tree/xml_parser.hpp>
78 #include <boost/locale.hpp>
79 #include <boost/range/adaptor/transformed.hpp>
80 #include <boost/range/algorithm/copy.hpp>
81 #include <boost/archive/iterators/base64_from_binary.hpp>
82 #include <boost/archive/iterators/insert_linebreaks.hpp>
83 #include <boost/archive/iterators/transform_width.hpp>
85 #include <tbb/concurrent_unordered_map.h>
89 102 [action] Information that [action] has happened
90 101 [action] Information that [action] has happened plus one row of data
92 202 [command] OK [command] has been executed
93 201 [command] OK [command] has been executed, plus one row of data
94 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
96 400 ERROR the command could not be understood
97 401 [command] ERROR invalid/missing channel
98 402 [command] ERROR parameter missing
99 403 [command] ERROR invalid parameter
100 404 [command] ERROR file not found
102 500 FAILED internal error
103 501 [command] FAILED internal error
104 502 [command] FAILED could not read file
105 503 [command] FAILED access denied
107 600 [command] FAILED [command] not implemented
110 namespace caspar { namespace protocol { namespace amcp {
112 using namespace core;
114 std::wstring read_file_base64(const boost::filesystem::path& file)
116 using namespace boost::archive::iterators;
118 boost::filesystem::ifstream filestream(file, std::ios::binary);
123 auto length = boost::filesystem::file_size(file);
124 std::vector<char> bytes;
125 bytes.resize(length);
126 filestream.read(bytes.data(), length);
128 std::string result(to_base64(bytes.data(), length));
129 return std::wstring(result.begin(), result.end());
132 std::wstring read_utf8_file(const boost::filesystem::path& file)
134 std::wstringstream result;
135 boost::filesystem::wifstream filestream(file);
142 result << filestream.rdbuf();
148 std::wstring read_latin1_file(const boost::filesystem::path& file)
150 boost::locale::generator gen;
151 gen.locale_cache_enabled(true);
152 gen.categories(boost::locale::codepage_facet);
154 std::stringstream result_stream;
155 boost::filesystem::ifstream filestream(file);
156 filestream.imbue(gen("en_US.ISO8859-1"));
161 result_stream << filestream.rdbuf();
164 std::string result = result_stream.str();
165 std::wstring widened_result;
167 // The first 255 codepoints in unicode is the same as in latin1
169 result | boost::adaptors::transformed(
170 [](char c) { return static_cast<unsigned char>(c); }),
171 std::back_inserter(widened_result));
173 return widened_result;
176 std::wstring read_file(const boost::filesystem::path& file)
178 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
180 if (!boost::filesystem::exists(file))
185 if (boost::filesystem::file_size(file) >= 3)
187 boost::filesystem::ifstream bom_stream(file);
190 bom_stream.read(header, 3);
193 if (std::memcmp(BOM, header, 3) == 0)
194 return read_utf8_file(file);
197 return read_latin1_file(file);
200 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
202 if (!boost::filesystem::is_regular_file(path))
205 auto media_info = media_info_repo->get(path.wstring());
210 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
212 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
213 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
214 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
216 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
217 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
218 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
220 auto relativePath = get_relative_without_extension(path, env::media_folder());
221 auto str = relativePath.generic_wstring();
223 if (str[0] == '\\' || str[0] == '/')
224 str = std::wstring(str.begin() + 1, str.end());
226 return std::wstring()
228 + L"\" " + media_info->clip_type +
230 + L" " + writeTimeWStr +
231 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
232 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
236 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
238 std::wstringstream replyString;
239 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
240 replyString << MediaInfo(itr->path(), media_info_repo);
242 return boost::to_upper_copy(replyString.str());
245 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
247 std::wstringstream replyString;
249 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
251 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
253 auto relativePath = get_relative_without_extension(itr->path(), env::template_folder());
255 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
256 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
257 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
259 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
260 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
262 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
264 auto dir = relativePath.parent_path();
265 auto file = boost::to_upper_copy(relativePath.filename().wstring());
266 relativePath = dir / file;
268 auto str = relativePath.generic_wstring();
269 boost::trim_if(str, boost::is_any_of("\\/"));
271 auto template_type = cg_registry->get_cg_producer_name(str);
273 replyString << L"\"" << str
274 << L"\" " << sizeWStr
275 << L" " << writeTimeWStr
276 << L" " << template_type
280 return replyString.str();
283 std::vector<spl::shared_ptr<core::video_channel>> get_channels(const command_context& ctx)
285 return cpplinq::from(ctx.channels)
286 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
290 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
292 return core::frame_producer_dependencies(
293 channel->frame_factory(),
295 channel->video_format_desc(),
296 ctx.producer_registry);
301 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
303 sink.short_description(L"Load a media file or resource in the background.");
304 sink.syntax(LR"(LOADBG [channel:int]{-[layer:int]} [clip:string] {[loop:LOOP]} {[transition:CUT,MIX,PUSH,WIPE,SLIDE] [duration:int] {[tween:string]|linear} {[direction:LEFT,RIGHT]|RIGHT}|CUT 0} {SEEK [frame:int]} {LENGTH [frames:int]} {FILTER [filter:string]} {[auto:AUTO]})");
306 ->text(L"Loads a producer in the background and prepares it for playout. ")
307 ->text(L"If no layer is specified the default layer index will be used.");
309 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
310 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
312 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
313 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
315 ->code(L"loop")->text(L" will cause the clip to loop.");
317 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
319 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
321 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
322 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
323 sink.para()->text(L"Examples:");
324 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
325 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
326 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
327 sink.example(L">> LOADBG 1-0 MY_FILE");
329 L">> PLAY 1-1 MY_FILE\n"
330 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
331 L"To automatically fade out a layer after a video file has been played to the end");
333 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
335 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
336 ->code(L"filter")->text(L" command.");
339 std::wstring loadbg_command(command_context& ctx)
341 transition_info transitionInfo;
345 std::wstring message;
346 for (size_t n = 0; n < ctx.parameters.size(); ++n)
347 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
349 static const boost::wregex expr(LR"(.*(?<TRANSITION>CUT|PUSH|SLIDE|WIPE|MIX)\s*(?<DURATION>\d+)\s*(?<TWEEN>(LINEAR)|(EASE[^\s]*))?\s*(?<DIRECTION>FROMLEFT|FROMRIGHT|LEFT|RIGHT)?.*)");
351 if (boost::regex_match(message, what, expr))
353 auto transition = what["TRANSITION"].str();
354 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
355 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
356 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
357 transitionInfo.tweener = tween;
359 if (transition == L"CUT")
360 transitionInfo.type = transition_type::cut;
361 else if (transition == L"MIX")
362 transitionInfo.type = transition_type::mix;
363 else if (transition == L"PUSH")
364 transitionInfo.type = transition_type::push;
365 else if (transition == L"SLIDE")
366 transitionInfo.type = transition_type::slide;
367 else if (transition == L"WIPE")
368 transitionInfo.type = transition_type::wipe;
370 if (direction == L"FROMLEFT")
371 transitionInfo.direction = transition_direction::from_left;
372 else if (direction == L"FROMRIGHT")
373 transitionInfo.direction = transition_direction::from_right;
374 else if (direction == L"LEFT")
375 transitionInfo.direction = transition_direction::from_right;
376 else if (direction == L"RIGHT")
377 transitionInfo.direction = transition_direction::from_left;
380 //Perform loading of the clip
381 core::diagnostics::scoped_call_context save;
382 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
383 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
385 auto channel = ctx.channel.channel;
386 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
388 if (pFP == frame_producer::empty())
389 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
391 bool auto_play = contains_param(L"AUTO", ctx.parameters);
393 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
395 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
397 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
399 return L"202 LOADBG OK\r\n";
402 void load_describer(core::help_sink& sink, const core::help_repository& repo)
404 sink.short_description(L"Load a media file or resource to the foreground.");
405 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
407 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
408 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
409 sink.para()->text(L"Examples:");
410 sink.example(L">> LOAD 1 MY_FILE");
411 sink.example(L">> LOAD 1-1 MY_FILE");
412 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
415 std::wstring load_command(command_context& ctx)
417 core::diagnostics::scoped_call_context save;
418 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
419 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
420 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
421 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
423 return L"202 LOAD OK\r\n";
426 void play_describer(core::help_sink& sink, const core::help_repository& repository)
428 sink.short_description(L"Play a media file or resource.");
429 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
431 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
432 ->text(L") is prepared, it will be executed.");
434 ->text(L"If additional parameters (see ")->see(L"LOADBG")
435 ->text(L") are provided then the provided clip will first be loaded to the background.");
436 sink.para()->text(L"Examples:");
437 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
438 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
439 sink.example(L">> PLAY 1-0 MY_FILE");
440 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
443 std::wstring play_command(command_context& ctx)
445 if (!ctx.parameters.empty())
448 ctx.channel.channel->stage().play(ctx.layer_index());
450 return L"202 PLAY OK\r\n";
453 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
455 sink.short_description(L"Pause playback of a layer.");
456 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
458 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
459 ->text(L" command can be used to resume playback again.");
460 sink.para()->text(L"Examples:");
461 sink.example(L">> PAUSE 1");
462 sink.example(L">> PAUSE 1-1");
465 std::wstring pause_command(command_context& ctx)
467 ctx.channel.channel->stage().pause(ctx.layer_index());
468 return L"202 PAUSE OK\r\n";
471 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
473 sink.short_description(L"Resume playback of a layer.");
474 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
476 ->text(L"Resumes playback of a foreground clip previously paused with the ")
477 ->see(L"PAUSE")->text(L" command.");
478 sink.para()->text(L"Examples:");
479 sink.example(L">> RESUME 1");
480 sink.example(L">> RESUME 1-1");
483 std::wstring resume_command(command_context& ctx)
485 ctx.channel.channel->stage().resume(ctx.layer_index());
486 return L"202 RESUME OK\r\n";
489 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
491 sink.short_description(L"Remove the foreground clip of a layer.");
492 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
494 ->text(L"Removes the foreground clip of the specified layer.");
495 sink.para()->text(L"Examples:");
496 sink.example(L">> STOP 1");
497 sink.example(L">> STOP 1-1");
500 std::wstring stop_command(command_context& ctx)
502 ctx.channel.channel->stage().stop(ctx.layer_index());
503 return L"202 STOP OK\r\n";
506 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
508 sink.short_description(L"Remove all clips of a layer or an entire channel.");
509 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
511 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
512 ->text(L"If no layer is specified then all layers in the specified ")
513 ->code(L"video_channel")->text(L" are cleared.");
514 sink.para()->text(L"Examples:");
515 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
516 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
519 std::wstring clear_command(command_context& ctx)
521 int index = ctx.layer_index(std::numeric_limits<int>::min());
522 if (index != std::numeric_limits<int>::min())
523 ctx.channel.channel->stage().clear(index);
525 ctx.channel.channel->stage().clear();
527 return L"202 CLEAR OK\r\n";
530 void call_describer(core::help_sink& sink, const core::help_repository& repo)
532 sink.short_description(L"Call a method on a producer.");
533 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
535 ->text(L"Calls method on the specified producer with the provided ")
536 ->code(L"param")->text(L" string.");
537 sink.para()->text(L"Examples:");
538 sink.example(L">> CALL 1 LOOP");
539 sink.example(L">> CALL 1-2 SEEK 25");
542 std::wstring call_command(command_context& ctx)
544 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters).get();
546 // TODO: because of std::async deferred timed waiting does not work
548 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
549 if (wait_res == std::future_status::timeout)
550 CASPAR_THROW_EXCEPTION(timed_out());*/
552 std::wstringstream replyString;
554 replyString << L"202 CALL OK\r\n";
556 replyString << L"201 CALL OK\r\n" << result << L"\r\n";
558 return replyString.str();
561 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
563 sink.short_description(L"Swap layers between channels.");
564 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
566 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
567 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
568 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
569 sink.para()->text(L"Examples:");
570 sink.example(L">> SWAP 1 2");
571 sink.example(L">> SWAP 1-1 2-3");
572 sink.example(L">> SWAP 1-1 1-2");
573 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
576 std::wstring swap_command(command_context& ctx)
578 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
580 if (ctx.layer_index(-1) != -1)
582 std::vector<std::string> strs;
583 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
585 auto ch1 = ctx.channel.channel;
586 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
588 int l1 = ctx.layer_index();
589 int l2 = boost::lexical_cast<int>(strs.at(1));
591 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
595 auto ch1 = ctx.channel.channel;
596 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
597 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
600 return L"202 SWAP OK\r\n";
603 void add_describer(core::help_sink& sink, const core::help_repository& repo)
605 sink.short_description(L"Add a consumer to a video channel.");
606 sink.syntax(L"ADD [video_channel:int]{-[consumer_index:int]} [consumer:string] [parameters:string]");
608 ->text(L"Adds a consumer to the specified video channel. The string ")
609 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
610 ->text(L"If a successful match is found a consumer will be created and added to the ")
611 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
612 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
613 ->see(L"the CasparCG config file")->text(L".");
615 ->text(L"Specifying ")->code(L"consumer_index")
616 ->text(L" overrides the index that the consumer itself decides and can later be used with the ")
617 ->see(L"REMOVE")->text(L" command to remove the consumer.");
618 sink.para()->text(L"Examples:");
619 sink.example(L">> ADD 1 DECKLINK 1");
620 sink.example(L">> ADD 1 BLUEFISH 2");
621 sink.example(L">> ADD 1 SCREEN");
622 sink.example(L">> ADD 1 AUDIO");
623 sink.example(L">> ADD 1 IMAGE filename");
624 sink.example(L">> ADD 2 SYNCTO 1");
625 sink.example(L">> ADD 1 FILE filename.mov");
626 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
628 L">> ADD 1-700 FILE filename.mov SEPARATE_KEY\n"
629 L">> REMOVE 1-700", L"overriding the consumer index to easier remove later.");
630 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
631 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
634 std::wstring add_command(command_context& ctx)
636 replace_placeholders(
637 L"<CLIENT_IP_ADDRESS>",
638 ctx.client->address(),
641 core::diagnostics::scoped_call_context save;
642 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
644 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx));
645 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
647 return L"202 ADD OK\r\n";
650 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
652 sink.short_description(L"Remove a consumer from a video channel.");
653 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
655 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
656 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
657 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
658 sink.para()->text(L"Examples:");
659 sink.example(L">> REMOVE 1 DECKLINK 1");
660 sink.example(L">> REMOVE 1 BLUEFISH 2");
661 sink.example(L">> REMOVE 1 SCREEN");
662 sink.example(L">> REMOVE 1 AUDIO");
663 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
666 std::wstring remove_command(command_context& ctx)
668 auto index = ctx.layer_index(std::numeric_limits<int>::min());
670 if (index == std::numeric_limits<int>::min())
672 replace_placeholders(
673 L"<CLIENT_IP_ADDRESS>",
674 ctx.client->address(),
677 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx))->index();
680 ctx.channel.channel->output().remove(index);
682 return L"202 REMOVE OK\r\n";
685 void print_describer(core::help_sink& sink, const core::help_repository& repo)
687 sink.short_description(L"Take a snapshot of a channel.");
688 sink.syntax(L"PRINT [video_channel:int]");
690 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
691 ->code(L"media")->text(L" folder.");
692 sink.para()->text(L"Examples:");
693 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
696 std::wstring print_command(command_context& ctx)
698 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage(), get_channels(ctx)));
700 return L"202 PRINT OK\r\n";
703 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
705 sink.short_description(L"Change the log level of the server.");
706 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
707 sink.para()->text(L"Changes the log level of the server.");
708 sink.para()->text(L"Examples:");
709 sink.example(L">> LOG LEVEL trace");
710 sink.example(L">> LOG LEVEL info");
713 std::wstring log_level_command(command_context& ctx)
715 log::set_log_level(ctx.parameters.at(0));
717 return L"202 LOG OK\r\n";
720 void log_category_describer(core::help_sink& sink, const core::help_repository& repo)
722 sink.short_description(L"Enable/disable a logging category in the server.");
723 sink.syntax(L"LOG CATEGORY [category:calltrace,communication] [enable:0,1]");
724 sink.para()->text(L"Enables or disables the specified logging category.");
725 sink.para()->text(L"Examples:");
726 sink.example(L">> LOG CATEGORY calltrace 1", L"to enable call trace");
727 sink.example(L">> LOG CATEGORY calltrace 0", L"to disable call trace");
730 std::wstring log_category_command(command_context& ctx)
732 log::set_log_category(ctx.parameters.at(0), ctx.parameters.at(1) == L"1");
734 return L"202 LOG OK\r\n";
737 void set_describer(core::help_sink& sink, const core::help_repository& repo)
739 sink.short_description(L"Change the value of a channel variable.");
740 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
741 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
743 ->item(L"MODE", L"Changes the video format of the channel.")
744 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
745 sink.para()->text(L"Examples:");
746 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
747 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
750 std::wstring set_command(command_context& ctx)
752 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
753 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
757 auto format_desc = core::video_format_desc(value);
758 if (format_desc.format != core::video_format::invalid)
760 ctx.channel.channel->video_format_desc(format_desc);
761 return L"202 SET MODE OK\r\n";
764 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video mode"));
766 else if (name == L"CHANNEL_LAYOUT")
768 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
772 ctx.channel.channel->audio_channel_layout(*channel_layout);
773 return L"202 SET CHANNEL_LAYOUT OK\r\n";
776 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid audio channel layout"));
779 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid channel variable"));
782 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
784 sink.short_description(L"Store a dataset.");
785 sink.syntax(L"DATA STORE [name:string] [data:string]");
786 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
787 sink.para()->text(L"Directories will be created if they do not exist.");
788 sink.para()->text(L"Examples:");
789 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
790 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
793 std::wstring data_store_command(command_context& ctx)
795 std::wstring filename = env::data_folder();
796 filename.append(ctx.parameters[0]);
797 filename.append(L".ftd");
799 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
800 auto found_data_path = find_case_insensitive(data_path);
803 data_path = *found_data_path;
805 if (!boost::filesystem::exists(data_path))
806 boost::filesystem::create_directories(data_path);
808 auto found_filename = find_case_insensitive(filename);
811 filename = *found_filename; // Overwrite case insensitive.
813 boost::filesystem::wofstream datafile(filename);
815 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
817 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
818 datafile << ctx.parameters[1] << std::flush;
821 return L"202 DATA STORE OK\r\n";
824 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
826 sink.short_description(L"Retrieve a dataset.");
827 sink.syntax(L"DATA RETRIEVE [name:string]");
828 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
829 sink.para()->text(L"Examples:");
830 sink.example(L">> DATA RETRIEVE my_data");
831 sink.example(L">> DATA RETRIEVE Folder1/my_data");
834 std::wstring data_retrieve_command(command_context& ctx)
836 std::wstring filename = env::data_folder();
837 filename.append(ctx.parameters[0]);
838 filename.append(L".ftd");
840 std::wstring file_contents;
842 auto found_file = find_case_insensitive(filename);
845 file_contents = read_file(boost::filesystem::path(*found_file));
847 if (file_contents.empty())
848 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
850 std::wstringstream reply;
851 reply << L"201 DATA RETRIEVE OK\r\n";
853 std::wstringstream file_contents_stream(file_contents);
856 bool firstLine = true;
857 while (std::getline(file_contents_stream, line))
871 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
873 sink.short_description(L"List stored datasets.");
874 sink.syntax(L"DATA LIST");
875 sink.para()->text(L"Returns a list of all stored datasets.");
878 std::wstring data_list_command(command_context& ctx)
880 std::wstringstream replyString;
881 replyString << L"200 DATA LIST OK\r\n";
883 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
885 if (boost::filesystem::is_regular_file(itr->path()))
887 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
890 auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
891 auto str = relativePath.generic_wstring();
893 if (str[0] == L'\\' || str[0] == L'/')
894 str = std::wstring(str.begin() + 1, str.end());
896 replyString << str << L"\r\n";
900 replyString << L"\r\n";
902 return boost::to_upper_copy(replyString.str());
905 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
907 sink.short_description(L"Remove a stored dataset.");
908 sink.syntax(L"DATA REMOVE [name:string]");
909 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
910 sink.para()->text(L"Examples:");
911 sink.example(L">> DATA REMOVE my_data");
912 sink.example(L">> DATA REMOVE Folder1/my_data");
915 std::wstring data_remove_command(command_context& ctx)
917 std::wstring filename = env::data_folder();
918 filename.append(ctx.parameters[0]);
919 filename.append(L".ftd");
921 if (!boost::filesystem::exists(filename))
922 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
924 if (!boost::filesystem::remove(filename))
925 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
927 return L"202 DATA REMOVE OK\r\n";
930 // Template Graphics Commands
932 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
934 sink.short_description(L"Prepare a template for displaying.");
935 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
937 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
938 ->text(L" (unless you supply the play-on-load flag, 1 for true). Data is either inline XML or a reference to a saved dataset.");
939 sink.para()->text(L"Examples:");
940 sink.example(L"CG 1 ADD 10 svtnews/info 1");
943 std::wstring cg_add_command(command_context& ctx)
945 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
947 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
948 std::wstring label; //_parameters[2]
949 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
950 unsigned int dataIndex = 3;
952 if (ctx.parameters.at(2).length() > 1)
954 label = ctx.parameters.at(2);
957 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
958 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
961 { //read play-on-load-flag
962 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
965 const wchar_t* pDataString = 0;
966 std::wstring dataFromFile;
967 if (ctx.parameters.size() > dataIndex)
969 const std::wstring& dataString = ctx.parameters.at(dataIndex);
971 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
972 pDataString = dataString.c_str();
975 //The data is not an XML-string, it must be a filename
976 std::wstring filename = env::data_folder();
977 filename.append(dataString);
978 filename.append(L".ftd");
980 auto found_file = find_case_insensitive(filename);
984 dataFromFile = read_file(boost::filesystem::path(*found_file));
985 pDataString = dataFromFile.c_str();
990 auto filename = ctx.parameters.at(1);
991 auto proxy = ctx.cg_registry->get_or_create_proxy(
992 spl::make_shared_ptr(ctx.channel.channel),
993 get_producer_dependencies(ctx.channel.channel, ctx),
994 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
997 if (proxy == core::cg_proxy::empty())
998 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
1000 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
1002 return L"202 CG OK\r\n";
1005 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
1007 sink.short_description(L"Play and display a template.");
1008 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
1009 sink.para()->text(L"Plays and displays the template in the specified layer.");
1010 sink.para()->text(L"Examples:");
1011 sink.example(L"CG 1 PLAY 0");
1014 std::wstring cg_play_command(command_context& ctx)
1016 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1017 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1019 return L"202 CG OK\r\n";
1022 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1024 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1026 if (proxy == cg_proxy::empty())
1027 CASPAR_THROW_EXCEPTION(expected_user_error() << msg_info(L"No CG proxy running on layer"));
1032 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1034 sink.short_description(L"Stop and remove a template.");
1035 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1037 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1038 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1039 sink.para()->text(L"Examples:");
1040 sink.example(L"CG 1 STOP 0");
1043 std::wstring cg_stop_command(command_context& ctx)
1045 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1046 get_expected_cg_proxy(ctx)->stop(layer, 0);
1048 return L"202 CG OK\r\n";
1051 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1053 sink.short_description(LR"(Trigger a "continue" in a template.)");
1054 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1056 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1057 ->text(L"This is used to control animations that has multiple discreet steps.");
1058 sink.para()->text(L"Examples:");
1059 sink.example(L"CG 1 NEXT 0");
1062 std::wstring cg_next_command(command_context& ctx)
1064 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1065 get_expected_cg_proxy(ctx)->next(layer);
1067 return L"202 CG OK\r\n";
1070 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1072 sink.short_description(L"Remove a template.");
1073 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1074 sink.para()->text(L"Removes the template from the specified layer.");
1075 sink.para()->text(L"Examples:");
1076 sink.example(L"CG 1 REMOVE 0");
1079 std::wstring cg_remove_command(command_context& ctx)
1081 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1082 get_expected_cg_proxy(ctx)->remove(layer);
1084 return L"202 CG OK\r\n";
1087 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1089 sink.short_description(L"Remove all templates on a video layer.");
1090 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1091 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1092 sink.para()->text(L"Examples:");
1093 sink.example(L"CG 1 CLEAR");
1096 std::wstring cg_clear_command(command_context& ctx)
1098 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1100 return L"202 CG OK\r\n";
1103 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1105 sink.short_description(L"Update a template with new data.");
1106 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1107 sink.para()->text(L"Sends new data to the template on specified layer. Data is either inline XML or a reference to a saved dataset.");
1110 std::wstring cg_update_command(command_context& ctx)
1112 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1114 std::wstring dataString = ctx.parameters.at(1);
1115 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1117 //The data is not XML or Json, it must be a filename
1118 std::wstring filename = env::data_folder();
1119 filename.append(dataString);
1120 filename.append(L".ftd");
1122 dataString = read_file(boost::filesystem::path(filename));
1125 get_expected_cg_proxy(ctx)->update(layer, dataString);
1127 return L"202 CG OK\r\n";
1130 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1132 sink.short_description(L"Invoke a method/label on a template.");
1133 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1134 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1135 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1138 std::wstring cg_invoke_command(command_context& ctx)
1140 std::wstringstream replyString;
1141 replyString << L"201 CG OK\r\n";
1142 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1143 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1144 replyString << result << L"\r\n";
1146 return replyString.str();
1149 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1151 sink.short_description(L"Get information about a running template or the template host.");
1152 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1153 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1154 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1157 std::wstring cg_info_command(command_context& ctx)
1159 std::wstringstream replyString;
1160 replyString << L"201 CG OK\r\n";
1162 if (ctx.parameters.empty())
1164 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1165 replyString << info << L"\r\n";
1169 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1170 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1172 replyString << desc << L"\r\n";
1175 return replyString.str();
1180 core::frame_transform get_current_transform(command_context& ctx)
1182 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1185 template<typename Func>
1186 std::wstring reply_value(command_context& ctx, const Func& extractor)
1188 auto value = extractor(get_current_transform(ctx));
1190 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1193 class transforms_applier
1195 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1197 std::vector<stage::transform_tuple_t> transforms_;
1198 command_context& ctx_;
1201 transforms_applier(command_context& ctx)
1204 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1207 ctx.parameters.pop_back();
1210 void add(stage::transform_tuple_t&& transform)
1212 transforms_.push_back(std::move(transform));
1215 void commit_deferred()
1217 auto& transforms = deferred_transforms_[ctx_.channel_index];
1218 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1226 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1227 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1230 ctx_.channel.channel->stage().apply_transforms(transforms_);
1233 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1235 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1237 sink.short_description(L"Let a layer act as alpha for the one obove.");
1238 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1240 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1241 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1242 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1243 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1244 ->text(L"instead it will be used as the key for the layer above.");
1245 sink.para()->text(L"Examples:");
1246 sink.example(L">> MIXER 1-0 KEYER 1");
1248 L">> MIXER 1-0 KEYER\n"
1249 L"<< 201 MIXER OK\n"
1250 L"<< 1", L"to retrieve the current state");
1253 std::wstring mixer_keyer_command(command_context& ctx)
1255 if (ctx.parameters.empty())
1256 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1258 transforms_applier transforms(ctx);
1259 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1260 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1262 transform.image_transform.is_key = value;
1264 }, 0, tweener(L"linear")));
1267 return L"202 MIXER OK\r\n";
1270 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1272 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1274 sink.short_description(L"Enable chroma keying on a layer.");
1275 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[enable:0,1] {[target_hue:float] [hue_width:float] [min_saturation:float] [min_brightness:float] [softness:float] [spill:float] [spill_darken:float] [show_mask:0,1]}}" + ANIMATION_SYNTAX);
1277 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1278 sink.para()->text(L"The chroma keying is done in the HSB/HSV color space.");
1279 sink.para()->text(L"Parameters:");
1281 ->item(L"enable", L"0 to disable chroma keying on layer. The rest of the parameters should not be given when disabling.")
1282 ->item(L"target_hue", L"The hue in degrees between 0-360 where the center of the hue window will open up.")
1283 ->item(L"hue_width", L"The width of the hue window within 0.0-1.0 where 1.0 means 100% of 360 degrees around target_hue.")
1284 ->item(L"min_saturation", L"The minimum saturation within 0.0-1.0 required for a color to be within the chroma window.")
1285 ->item(L"min_brightness", L"The minimum brightness within 0.0-1.0 required for a color to be within the chroma window.")
1286 ->item(L"softness", L"The softness of the chroma keying window.")
1287 ->item(L"spill", L"Controls the amount of spill. A value of 1.0 does not suppress any spill. A lower value gradually turns the spill into grayscale and more transparent.")
1288 ->item(L"spill_darken", L"Controls the shade of gray that the spill suppression is done towards. Lower values goes towards white and higher values goes towards black.")
1289 ->item(L"show_mask", L"If enabled, only shows the mask. Useful while editing the chroma key settings.")
1291 sink.example(L">> MIXER 1-1 CHROMA 1 120 0.1 0 0 0.1 1 2 0", L"for enabling chroma keying centered around a hue of 120 degrees (green) and with a 10% hue width");
1292 sink.example(L">> MIXER 1-1 CHROMA 0", L"for disabling chroma keying");
1294 L">> MIXER 1-1 CHROMA 0\n"
1295 L"<< 202 MIXER OK\n"
1296 L"<< 1 120 0.1 0 0 0.1 1 2 0", L"for getting the current chroma key mode");
1297 sink.para()->text(L"Deprecated legacy syntax:");
1298 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] [spill:float]}}" + ANIMATION_SYNTAX);
1299 sink.para()->text(L"Deprecated legacy examples:");
1300 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 1.0 25 easeinsine");
1301 sink.example(L">> MIXER 1-1 CHROMA none");
1304 std::wstring mixer_chroma_command(command_context& ctx)
1306 if (ctx.parameters.empty())
1308 auto chroma = get_current_transform(ctx).image_transform.chroma;
1309 return L"201 MIXER OK\r\n"
1310 + std::wstring(chroma.enable ? L"1 " : L"0 ")
1311 + boost::lexical_cast<std::wstring>(chroma.target_hue) + L" "
1312 + boost::lexical_cast<std::wstring>(chroma.hue_width) + L" "
1313 + boost::lexical_cast<std::wstring>(chroma.min_saturation) + L" "
1314 + boost::lexical_cast<std::wstring>(chroma.min_brightness) + L" "
1315 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1316 + boost::lexical_cast<std::wstring>(chroma.spill) + L" "
1317 + boost::lexical_cast<std::wstring>(chroma.spill_darken) + L" "
1318 + std::wstring(chroma.show_mask ? L"1" : L"0") + L"\r\n";
1321 transforms_applier transforms(ctx);
1322 core::chroma chroma;
1327 auto legacy_mode = core::get_chroma_mode(ctx.parameters.at(0));
1332 duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1333 tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1335 if (*legacy_mode == chroma::legacy_type::none)
1337 chroma.enable = false;
1341 chroma.enable = true;
1342 chroma.hue_width = 0.5 - boost::lexical_cast<double>(ctx.parameters.at(1)) * 0.5;
1343 chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(1));
1344 chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(1));
1345 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2)) - boost::lexical_cast<double>(ctx.parameters.at(1));
1346 chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(3));
1347 chroma.spill_darken = 2;
1349 if (*legacy_mode == chroma::legacy_type::green)
1350 chroma.target_hue = 120;
1351 else if (*legacy_mode == chroma::legacy_type::blue)
1352 chroma.target_hue = 240;
1357 duration = ctx.parameters.size() > 9 ? boost::lexical_cast<int>(ctx.parameters.at(9)) : 0;
1358 tween = ctx.parameters.size() > 10 ? ctx.parameters.at(10) : L"linear";
1360 chroma.enable = ctx.parameters.at(0) == L"1";
1364 chroma.target_hue = boost::lexical_cast<double>(ctx.parameters.at(1));
1365 chroma.hue_width = boost::lexical_cast<double>(ctx.parameters.at(2));
1366 chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(3));
1367 chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(4));
1368 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(5));
1369 chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(6));
1370 chroma.spill_darken = boost::lexical_cast<double>(ctx.parameters.at(7));
1371 chroma.show_mask = boost::lexical_cast<double>(ctx.parameters.at(8));
1376 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1378 transform.image_transform.chroma = chroma;
1380 }, duration, tween));
1383 return L"202 MIXER OK\r\n";
1386 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1388 sink.short_description(L"Set the blend mode for a layer.");
1389 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1391 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1392 ->text(L"If no argument is given the current blend mode is returned.");
1394 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1395 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1396 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1397 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1398 sink.para()->text(L"Examples:");
1399 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1401 L">> MIXER 1-1 BLEND\n"
1402 L"<< 201 MIXER OK\n"
1403 L"<< SCREEN", L"for getting the current blend mode");
1404 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1407 std::wstring mixer_blend_command(command_context& ctx)
1409 if (ctx.parameters.empty())
1410 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1412 transforms_applier transforms(ctx);
1413 auto value = get_blend_mode(ctx.parameters.at(0));
1414 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1416 transform.image_transform.blend_mode = value;
1418 }, 0, tweener(L"linear")));
1421 return L"202 MIXER OK\r\n";
1424 template<typename Getter, typename Setter>
1425 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1427 if (ctx.parameters.empty())
1428 return reply_value(ctx, getter);
1430 transforms_applier transforms(ctx);
1431 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1432 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1433 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1435 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1437 setter(transform, value);
1439 }, duration, tween));
1442 return L"202 MIXER OK\r\n";
1445 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1447 sink.short_description(L"Change the opacity of a layer.");
1448 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1449 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1450 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1451 sink.para()->text(L"Examples:");
1452 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1454 L">> MIXER 1-0 OPACITY\n"
1455 L"<< 201 MIXER OK\n"
1456 L"<< 0.5", L"to retrieve the current opacity");
1459 std::wstring mixer_opacity_command(command_context& ctx)
1461 return single_double_animatable_mixer_command(
1463 [](const frame_transform& t) { return t.image_transform.opacity; },
1464 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1467 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1469 sink.short_description(L"Change the brightness of a layer.");
1470 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1471 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1472 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1473 sink.para()->text(L"Examples:");
1474 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1476 L">> MIXER 1-0 BRIGHTNESS\n"
1477 L"<< 201 MIXER OK\n"
1478 L"0.5", L"to retrieve the current brightness");
1481 std::wstring mixer_brightness_command(command_context& ctx)
1483 return single_double_animatable_mixer_command(
1485 [](const frame_transform& t) { return t.image_transform.brightness; },
1486 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1489 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1491 sink.short_description(L"Change the saturation of a layer.");
1492 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1493 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1494 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1495 sink.para()->text(L"Examples:");
1496 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1498 L">> MIXER 1-0 SATURATION\n"
1499 L"<< 201 MIXER OK\n"
1500 L"<< 0.5", L"to retrieve the current saturation");
1503 std::wstring mixer_saturation_command(command_context& ctx)
1505 return single_double_animatable_mixer_command(
1507 [](const frame_transform& t) { return t.image_transform.saturation; },
1508 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1511 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1513 sink.short_description(L"Change the contrast of a layer.");
1514 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1515 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1516 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1517 sink.para()->text(L"Examples:");
1518 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1520 L">> MIXER 1-0 CONTRAST\n"
1521 L"<< 201 MIXER OK\n"
1522 L"<< 0.5", L"to retrieve the current contrast");
1525 std::wstring mixer_contrast_command(command_context& ctx)
1527 return single_double_animatable_mixer_command(
1529 [](const frame_transform& t) { return t.image_transform.contrast; },
1530 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1533 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1535 sink.short_description(L"Adjust the video levels of a layer.");
1536 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);
1538 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1540 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1541 ->item(L"gamma", L"Adjusts the gamma of the image.")
1542 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1543 sink.para()->text(L"Examples:");
1544 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");
1545 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");
1546 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1548 L">> MIXER 1-10 LEVELS\n"
1549 L"<< 201 MIXER OK\n"
1550 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1553 std::wstring mixer_levels_command(command_context& ctx)
1555 if (ctx.parameters.empty())
1557 auto levels = get_current_transform(ctx).image_transform.levels;
1558 return L"201 MIXER OK\r\n"
1559 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1560 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1561 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1562 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1563 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1566 transforms_applier transforms(ctx);
1568 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1569 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1570 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1571 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1572 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1573 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1574 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1576 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1578 transform.image_transform.levels = value;
1580 }, duration, tween));
1583 return L"202 MIXER OK\r\n";
1586 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1588 sink.short_description(L"Change the fill position and scale of a layer.");
1589 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1591 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1592 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1593 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1594 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1596 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1597 ->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.");
1598 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1600 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1601 ->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, ")
1602 ->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");
1604 ->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.")
1605 ->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.")
1606 ->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.")
1607 ->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.");
1608 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1609 sink.para()->text(L"Examples:");
1610 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1612 L">> MIXER 1-0 FILL\n"
1613 L"<< 201 MIXER OK\n"
1614 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1617 std::wstring mixer_fill_command(command_context& ctx)
1619 if (ctx.parameters.empty())
1621 auto transform = get_current_transform(ctx).image_transform;
1622 auto translation = transform.fill_translation;
1623 auto scale = transform.fill_scale;
1624 return L"201 MIXER OK\r\n"
1625 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1626 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1627 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1628 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1631 transforms_applier transforms(ctx);
1632 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1633 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1634 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1635 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1636 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1637 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1639 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1641 transform.image_transform.fill_translation[0] = x;
1642 transform.image_transform.fill_translation[1] = y;
1643 transform.image_transform.fill_scale[0] = x_s;
1644 transform.image_transform.fill_scale[1] = y_s;
1646 }, duration, tween));
1649 return L"202 MIXER OK\r\n";
1652 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1654 sink.short_description(L"Change the clipping viewport of a layer.");
1655 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1657 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1658 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1659 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1661 ->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.")
1662 ->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.")
1663 ->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.")
1664 ->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.");
1665 sink.para()->text(L"Examples:");
1666 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1668 L">> MIXER 1-0 CLIP\n"
1669 L"<< 201 MIXER OK\n"
1670 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1673 std::wstring mixer_clip_command(command_context& ctx)
1675 if (ctx.parameters.empty())
1677 auto transform = get_current_transform(ctx).image_transform;
1678 auto translation = transform.clip_translation;
1679 auto scale = transform.clip_scale;
1681 return L"201 MIXER OK\r\n"
1682 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1683 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1684 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1685 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1688 transforms_applier transforms(ctx);
1689 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1690 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1691 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1692 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1693 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1694 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1696 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1698 transform.image_transform.clip_translation[0] = x;
1699 transform.image_transform.clip_translation[1] = y;
1700 transform.image_transform.clip_scale[0] = x_s;
1701 transform.image_transform.clip_scale[1] = y_s;
1703 }, duration, tween));
1706 return L"202 MIXER OK\r\n";
1709 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1711 sink.short_description(L"Change the anchor point of a layer.");
1712 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1713 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1715 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1716 ->text(L" will be done from.");
1718 ->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.")
1719 ->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.");
1720 sink.para()->text(L"Examples:");
1721 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1723 L">> MIXER 1-10 ANCHOR\n"
1724 L"<< 201 MIXER OK\n"
1725 L"<< 0.5 0.6", L"gets the anchor point");
1728 std::wstring mixer_anchor_command(command_context& ctx)
1730 if (ctx.parameters.empty())
1732 auto transform = get_current_transform(ctx).image_transform;
1733 auto anchor = transform.anchor;
1734 return L"201 MIXER OK\r\n"
1735 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1736 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1739 transforms_applier transforms(ctx);
1740 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1741 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1742 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1743 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1745 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1747 transform.image_transform.anchor[0] = x;
1748 transform.image_transform.anchor[1] = y;
1750 }, duration, tween));
1753 return L"202 MIXER OK\r\n";
1756 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1758 sink.short_description(L"Crop a layer.");
1759 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);
1761 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1762 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1763 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1765 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1766 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1767 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1768 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1769 sink.para()->text(L"Examples:");
1770 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");
1772 L">> MIXER 1-0 CROP\n"
1773 L"<< 201 MIXER OK\n"
1774 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1777 std::wstring mixer_crop_command(command_context& ctx)
1779 if (ctx.parameters.empty())
1781 auto crop = get_current_transform(ctx).image_transform.crop;
1782 return L"201 MIXER OK\r\n"
1783 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1784 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1785 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1786 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1789 transforms_applier transforms(ctx);
1790 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1791 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1792 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1793 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1794 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1795 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1797 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1799 transform.image_transform.crop.ul[0] = ul_x;
1800 transform.image_transform.crop.ul[1] = ul_y;
1801 transform.image_transform.crop.lr[0] = lr_x;
1802 transform.image_transform.crop.lr[1] = lr_y;
1804 }, duration, tween));
1807 return L"202 MIXER OK\r\n";
1810 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1812 sink.short_description(L"Rotate a layer.");
1813 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1815 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1816 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1817 sink.para()->text(L"Examples:");
1818 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1820 L">> MIXER 1-0 ROTATION\n"
1821 L"<< 201 MIXER OK\n"
1822 L"<< 45", L"to retrieve the current angle");
1825 std::wstring mixer_rotation_command(command_context& ctx)
1827 static const double PI = 3.141592653589793;
1829 return single_double_animatable_mixer_command(
1831 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1832 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1835 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1837 sink.short_description(L"Adjust the perspective transform of a layer.");
1838 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);
1840 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1842 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1843 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1844 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1845 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1846 sink.para()->text(L"Examples:");
1847 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1849 L">> MIXER 1-10 PERSPECTIVE\n"
1850 L"<< 201 MIXER OK\n"
1851 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1854 std::wstring mixer_perspective_command(command_context& ctx)
1856 if (ctx.parameters.empty())
1858 auto perspective = get_current_transform(ctx).image_transform.perspective;
1861 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1862 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1863 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1864 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1865 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1866 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1867 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1868 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1871 transforms_applier transforms(ctx);
1872 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1873 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1874 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1875 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1876 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1877 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1878 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1879 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1880 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1881 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1883 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1885 transform.image_transform.perspective.ul[0] = ul_x;
1886 transform.image_transform.perspective.ul[1] = ul_y;
1887 transform.image_transform.perspective.ur[0] = ur_x;
1888 transform.image_transform.perspective.ur[1] = ur_y;
1889 transform.image_transform.perspective.lr[0] = lr_x;
1890 transform.image_transform.perspective.lr[1] = lr_y;
1891 transform.image_transform.perspective.ll[0] = ll_x;
1892 transform.image_transform.perspective.ll[1] = ll_y;
1894 }, duration, tween));
1897 return L"202 MIXER OK\r\n";
1900 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1902 sink.short_description(L"Enable or disable mipmapping for a layer.");
1903 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1905 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1906 ->text(L"If no argument is given the current state is returned.");
1907 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1908 sink.para()->text(L"Examples:");
1909 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1911 L">> MIXER 1-10 MIPMAP\n"
1912 L"<< 201 MIXER OK\n"
1913 L"<< 1", L"for getting the current state");
1916 std::wstring mixer_mipmap_command(command_context& ctx)
1918 if (ctx.parameters.empty())
1919 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1921 transforms_applier transforms(ctx);
1922 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1923 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1925 transform.image_transform.use_mipmap = value;
1927 }, 0, tweener(L"linear")));
1930 return L"202 MIXER OK\r\n";
1933 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1935 sink.short_description(L"Change the volume of a layer.");
1936 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1937 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1938 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1939 sink.para()->text(L"Examples:");
1940 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1941 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1943 L">> MIXER 1-0 VOLUME\n"
1944 L"<< 201 MIXER OK\n"
1945 L"<< 0.8", L"to retrieve the current volume");
1948 std::wstring mixer_volume_command(command_context& ctx)
1950 return single_double_animatable_mixer_command(
1952 [](const frame_transform& t) { return t.audio_transform.volume; },
1953 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1956 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1958 sink.short_description(L"Change the volume of an entire channel.");
1959 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1960 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1961 sink.para()->text(L"Examples:");
1962 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1963 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1964 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1967 std::wstring mixer_mastervolume_command(command_context& ctx)
1969 if (ctx.parameters.empty())
1971 auto volume = ctx.channel.channel->mixer().get_master_volume();
1972 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1975 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1976 ctx.channel.channel->mixer().set_master_volume(master_volume);
1978 return L"202 MIXER OK\r\n";
1981 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1983 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1984 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1985 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1986 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1987 sink.para()->text(L"Examples:");
1988 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1989 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1991 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1992 L"<< 201 MIXER OK\n"
1996 std::wstring mixer_straight_alpha_command(command_context& ctx)
1998 if (ctx.parameters.empty())
2000 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
2001 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
2004 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
2005 ctx.channel.channel->mixer().set_straight_alpha_output(state);
2007 return L"202 MIXER OK\r\n";
2010 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2012 sink.short_description(L"Create a grid of video layers.");
2013 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
2015 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
2016 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
2017 sink.para()->text(L"Examples:");
2018 sink.example(L">> MIXER 1 GRID 2");
2021 std::wstring mixer_grid_command(command_context& ctx)
2023 transforms_applier transforms(ctx);
2024 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
2025 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
2026 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
2027 double delta = 1.0 / static_cast<double>(n);
2028 for (int x = 0; x < n; ++x)
2030 for (int y = 0; y < n; ++y)
2032 int index = x + y*n + 1;
2033 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
2035 transform.image_transform.fill_translation[0] = x*delta;
2036 transform.image_transform.fill_translation[1] = y*delta;
2037 transform.image_transform.fill_scale[0] = delta;
2038 transform.image_transform.fill_scale[1] = delta;
2039 transform.image_transform.clip_translation[0] = x*delta;
2040 transform.image_transform.clip_translation[1] = y*delta;
2041 transform.image_transform.clip_scale[0] = delta;
2042 transform.image_transform.clip_scale[1] = delta;
2044 }, duration, tween));
2049 return L"202 MIXER OK\r\n";
2052 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
2054 sink.short_description(L"Commit all deferred mixer transforms.");
2055 sink.syntax(L"MIXER [video_channel:int] COMMIT");
2056 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
2057 sink.para()->text(L"Examples:");
2059 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
2060 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
2061 L">> MIXER 1 COMMIT");
2064 std::wstring mixer_commit_command(command_context& ctx)
2066 transforms_applier transforms(ctx);
2067 transforms.commit_deferred();
2069 return L"202 MIXER OK\r\n";
2072 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
2074 sink.short_description(L"Clear all transformations on a channel or layer.");
2075 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
2076 sink.para()->text(L"Clears all transformations on a channel or layer.");
2077 sink.para()->text(L"Examples:");
2078 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
2079 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
2082 std::wstring mixer_clear_command(command_context& ctx)
2084 int layer = ctx.layer_id;
2087 ctx.channel.channel->stage().clear_transforms();
2089 ctx.channel.channel->stage().clear_transforms(layer);
2091 return L"202 MIXER OK\r\n";
2094 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2096 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2097 sink.syntax(L"CHANNEL_GRID");
2098 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2100 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2101 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2104 std::wstring channel_grid_command(command_context& ctx)
2107 auto self = ctx.channels.back();
2109 core::diagnostics::scoped_call_context save;
2110 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2112 std::vector<std::wstring> params;
2113 params.push_back(L"SCREEN");
2114 params.push_back(L"0");
2115 params.push_back(L"NAME");
2116 params.push_back(L"Channel Grid Window");
2117 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage(), get_channels(ctx));
2119 self.channel->output().add(screen);
2121 for (auto& channel : ctx.channels)
2123 if (channel.channel != self.channel)
2125 core::diagnostics::call_context::for_thread().layer = index;
2126 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2127 self.channel->stage().load(index, producer, false);
2128 self.channel->stage().play(index);
2133 auto num_channels = ctx.channels.size() - 1;
2134 int square_side_length = std::ceil(std::sqrt(num_channels));
2136 ctx.channel_index = self.channel->index();
2138 ctx.parameters.clear();
2139 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2140 mixer_grid_command(ctx);
2142 return L"202 CHANNEL_GRID OK\r\n";
2145 // Thumbnail Commands
2147 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2149 sink.short_description(L"List all thumbnails.");
2150 sink.syntax(L"THUMBNAIL LIST");
2151 sink.para()->text(L"Lists all thumbnails.");
2152 sink.para()->text(L"Examples:");
2154 L">> THUMBNAIL LIST\n"
2155 L"<< 200 THUMBNAIL LIST OK\n"
2156 L"<< \"AMB\" 20130301T124409 1149\n"
2157 L"<< \"foo/bar\" 20130523T234001 244");
2160 std::wstring thumbnail_list_command(command_context& ctx)
2162 std::wstringstream replyString;
2163 replyString << L"200 THUMBNAIL LIST OK\r\n";
2165 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnail_folder()), end; itr != end; ++itr)
2167 if (boost::filesystem::is_regular_file(itr->path()))
2169 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2172 auto relativePath = get_relative_without_extension(itr->path(), env::thumbnail_folder());
2173 auto str = relativePath.generic_wstring();
2175 if (str[0] == '\\' || str[0] == '/')
2176 str = std::wstring(str.begin() + 1, str.end());
2178 auto mtime = boost::filesystem::last_write_time(itr->path());
2179 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2180 auto file_size = boost::filesystem::file_size(itr->path());
2182 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2186 replyString << L"\r\n";
2188 return boost::to_upper_copy(replyString.str());
2191 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2193 sink.short_description(L"Retrieve a thumbnail.");
2194 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2195 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2196 sink.para()->text(L"Examples:");
2198 L">> THUMBNAIL RETRIEVE foo/bar\n"
2199 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2200 L"<< ...base64 data...");
2203 std::wstring thumbnail_retrieve_command(command_context& ctx)
2205 std::wstring filename = env::thumbnail_folder();
2206 filename.append(ctx.parameters.at(0));
2207 filename.append(L".png");
2209 std::wstring file_contents;
2211 auto found_file = find_case_insensitive(filename);
2214 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2216 if (file_contents.empty())
2217 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2219 std::wstringstream reply;
2221 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2222 reply << file_contents;
2227 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2229 sink.short_description(L"Regenerate a thumbnail.");
2230 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2231 sink.para()->text(L"Regenerates a thumbnail.");
2234 std::wstring thumbnail_generate_command(command_context& ctx)
2238 ctx.thumb_gen->generate(ctx.parameters.at(0));
2239 return L"202 THUMBNAIL GENERATE OK\r\n";
2242 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2245 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2247 sink.short_description(L"Regenerate all thumbnails.");
2248 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2249 sink.para()->text(L"Regenerates all thumbnails.");
2252 std::wstring thumbnail_generateall_command(command_context& ctx)
2256 ctx.thumb_gen->generate_all();
2257 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2260 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2265 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2267 sink.short_description(L"Get information about a media file.");
2268 sink.syntax(L"CINF [filename:string]");
2269 sink.para()->text(L"Returns information about a media file.");
2272 std::wstring cinf_command(command_context& ctx)
2275 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2277 auto path = itr->path();
2278 auto file = path.replace_extension(L"").filename().wstring();
2279 if (boost::iequals(file, ctx.parameters.at(0)))
2280 info += MediaInfo(itr->path(), ctx.media_info_repo);
2284 CASPAR_THROW_EXCEPTION(file_not_found());
2286 std::wstringstream replyString;
2287 replyString << L"200 CINF OK\r\n";
2288 replyString << info << "\r\n";
2290 return replyString.str();
2293 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2295 sink.short_description(L"List all media files.");
2296 sink.syntax(L"CLS");
2298 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2299 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2302 std::wstring cls_command(command_context& ctx)
2304 std::wstringstream replyString;
2305 replyString << L"200 CLS OK\r\n";
2306 replyString << ListMedia(ctx.media_info_repo);
2307 replyString << L"\r\n";
2308 return boost::to_upper_copy(replyString.str());
2311 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2313 sink.short_description(L"List all fonts.");
2314 sink.syntax(L"FLS");
2316 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2317 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2318 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2321 std::wstring fls_command(command_context& ctx)
2323 std::wstringstream replyString;
2324 replyString << L"200 FLS OK\r\n";
2326 for (auto& font : core::text::list_fonts())
2327 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2329 replyString << L"\r\n";
2331 return replyString.str();
2334 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2336 sink.short_description(L"List all templates.");
2337 sink.syntax(L"TLS");
2339 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2340 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2343 std::wstring tls_command(command_context& ctx)
2345 std::wstringstream replyString;
2346 replyString << L"200 TLS OK\r\n";
2348 replyString << ListTemplates(ctx.cg_registry);
2349 replyString << L"\r\n";
2351 return replyString.str();
2354 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2356 sink.short_description(L"Get version information.");
2357 sink.syntax(L"VERSION {[component:string]}");
2358 sink.para()->text(L"Returns the version of specified component.");
2359 sink.para()->text(L"Examples:");
2362 L"<< 201 VERSION OK\n"
2363 L"<< 2.1.0.f207a33 STABLE");
2365 L">> VERSION SERVER\n"
2366 L"<< 201 VERSION OK\n"
2367 L"<< 2.1.0.f207a33 STABLE");
2369 L">> VERSION FLASH\n"
2370 L"<< 201 VERSION OK\n"
2373 L">> VERSION TEMPLATEHOST\n"
2374 L"<< 201 VERSION OK\n"
2378 L"<< 201 VERSION OK\n"
2382 std::wstring version_command(command_context& ctx)
2384 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2386 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2388 return L"201 VERSION OK\r\n" + version + L"\r\n";
2391 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2394 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2396 sink.short_description(L"Get a list of the available channels.");
2397 sink.syntax(L"INFO");
2398 sink.para()->text(L"Retrieves a list of the available channels.");
2402 L"<< 1 720p5000 PLAYING\n"
2403 L"<< 2 PAL PLAYING");
2406 std::wstring info_command(command_context& ctx)
2408 std::wstringstream replyString;
2409 // This is needed for backwards compatibility with old clients
2410 replyString << L"200 INFO OK\r\n";
2411 for (size_t n = 0; n < ctx.channels.size(); ++n)
2412 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2413 replyString << L"\r\n";
2414 return replyString.str();
2417 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2419 std::wstringstream replyString;
2421 if (command.empty())
2422 replyString << L"201 INFO OK\r\n";
2424 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2426 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2427 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2428 replyString << L"\r\n";
2429 return replyString.str();
2432 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2434 sink.short_description(L"Get information about a channel or a layer.");
2435 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2436 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2437 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2440 std::wstring info_channel_command(command_context& ctx)
2442 boost::property_tree::wptree info;
2443 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2445 if (layer == std::numeric_limits<int>::min())
2447 info.add_child(L"channel", ctx.channel.channel->info())
2448 .add(L"index", ctx.channel_index);
2452 if (ctx.parameters.size() >= 1)
2454 if (boost::iequals(ctx.parameters.at(0), L"B"))
2455 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2457 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2461 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2465 return create_info_xml_reply(info);
2468 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2470 sink.short_description(L"Get information about a template.");
2471 sink.syntax(L"INFO TEMPLATE [template:string]");
2472 sink.para()->text(L"Gets information about the specified template.");
2475 std::wstring info_template_command(command_context& ctx)
2477 auto filename = ctx.parameters.at(0);
2479 std::wstringstream str;
2480 str << u16(ctx.cg_registry->read_meta_info(filename));
2481 boost::property_tree::wptree info;
2482 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2484 return create_info_xml_reply(info, L"TEMPLATE");
2487 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2489 sink.short_description(L"Get the contents of the configuration used.");
2490 sink.syntax(L"INFO CONFIG");
2491 sink.para()->text(L"Gets the contents of the configuration used.");
2494 std::wstring info_config_command(command_context& ctx)
2496 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2499 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2501 sink.short_description(L"Get information about the paths used.");
2502 sink.syntax(L"INFO PATHS");
2503 sink.para()->text(L"Gets information about the paths used.");
2506 std::wstring info_paths_command(command_context& ctx)
2508 boost::property_tree::wptree info;
2510 info.add(L"paths.media-path", caspar::env::media_folder());
2511 info.add(L"paths.log-path", caspar::env::log_folder());
2512 info.add(L"paths.data-path", caspar::env::data_folder());
2513 info.add(L"paths.template-path", caspar::env::template_folder());
2514 info.add(L"paths.thumbnail-path", caspar::env::thumbnail_folder());
2515 info.add(L"paths.font-path", caspar::env::font_folder());
2516 info.add(L"paths.initial-path", caspar::env::initial_folder() + L"/");
2518 return create_info_xml_reply(info, L"PATHS");
2521 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2523 sink.short_description(L"Get system information.");
2524 sink.syntax(L"INFO SYSTEM");
2525 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2528 std::wstring info_system_command(command_context& ctx)
2530 boost::property_tree::wptree info;
2532 info.add(L"system.name", caspar::system_product_name());
2533 info.add(L"system.os.description", caspar::os_description());
2534 info.add(L"system.cpu", caspar::cpu_info());
2536 ctx.system_info_repo->fill_information(info);
2538 return create_info_xml_reply(info, L"SYSTEM");
2541 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2543 sink.short_description(L"Get detailed information about all channels.");
2544 sink.syntax(L"INFO SERVER");
2545 sink.para()->text(L"Gets detailed information about all channels.");
2548 std::wstring info_server_command(command_context& ctx)
2550 boost::property_tree::wptree info;
2553 for (auto& channel : ctx.channels)
2554 info.add_child(L"channels.channel", channel.channel->info())
2555 .add(L"index", ++index);
2557 return create_info_xml_reply(info, L"SERVER");
2560 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2562 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2563 sink.syntax(L"INFO QUEUES");
2564 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2567 std::wstring info_queues_command(command_context& ctx)
2569 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2572 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2574 sink.short_description(L"Lists all known threads in the server.");
2575 sink.syntax(L"INFO THREADS");
2576 sink.para()->text(L"Lists all known threads in the server.");
2579 std::wstring info_threads_command(command_context& ctx)
2581 std::wstringstream replyString;
2582 replyString << L"200 INFO THREADS OK\r\n";
2584 for (auto& thread : get_thread_infos())
2586 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2589 replyString << L"\r\n";
2590 return replyString.str();
2593 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2595 sink.short_description(L"Get the current delay on a channel or a layer.");
2596 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2597 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2600 std::wstring info_delay_command(command_context& ctx)
2602 boost::property_tree::wptree info;
2603 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2605 if (layer == std::numeric_limits<int>::min())
2606 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2608 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2609 .add(L"index", layer);
2611 return create_info_xml_reply(info, L"DELAY");
2614 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2616 sink.short_description(L"Open the diagnostics window.");
2617 sink.syntax(L"DIAG");
2618 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2621 std::wstring diag_command(command_context& ctx)
2623 core::diagnostics::osd::show_graphs(true);
2625 return L"202 DIAG OK\r\n";
2628 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2630 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2631 sink.syntax(L"GL INFO");
2632 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2635 std::wstring gl_info_command(command_context& ctx)
2637 auto device = ctx.ogl_device;
2640 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2642 std::wstringstream result;
2643 result << L"201 GL INFO OK\r\n";
2645 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2646 auto info = device->info();
2647 boost::property_tree::write_xml(result, info, w);
2650 return result.str();
2653 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2655 sink.short_description(L"Release pooled OpenGL resources.");
2656 sink.syntax(L"GL GC");
2657 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2660 std::wstring gl_gc_command(command_context& ctx)
2662 auto device = ctx.ogl_device;
2665 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2667 device->gc().wait();
2669 return L"202 GL GC OK\r\n";
2672 static const int WIDTH = 80;
2674 struct max_width_sink : public core::help_sink
2676 std::size_t max_width = 0;
2678 void begin_item(const std::wstring& name) override
2680 max_width = std::max(name.length(), max_width);
2684 struct short_description_sink : public core::help_sink
2687 std::wstringstream& out;
2689 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2691 void begin_item(const std::wstring& name) override
2693 out << std::left << std::setw(width + 1) << name;
2696 void short_description(const std::wstring& short_description) override
2698 out << short_description << L"\r\n";
2702 struct simple_paragraph_builder : core::paragraph_builder
2704 std::wostringstream out;
2705 std::wstringstream& commit_to;
2707 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2708 ~simple_paragraph_builder()
2710 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2712 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2714 out << std::move(text);
2715 return shared_from_this();
2717 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2718 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2719 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2720 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2723 struct simple_definition_list_builder : core::definition_list_builder
2725 std::wstringstream& out;
2727 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2728 ~simple_definition_list_builder()
2733 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2735 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2736 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2737 return shared_from_this();
2741 struct long_description_sink : public core::help_sink
2743 std::wstringstream& out;
2745 long_description_sink(std::wstringstream& out) : out(out) { }
2747 void syntax(const std::wstring& syntax) override
2749 out << L"Syntax:\n";
2750 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2753 spl::shared_ptr<core::paragraph_builder> para() override
2755 return spl::make_shared<simple_paragraph_builder>(out);
2758 spl::shared_ptr<core::definition_list_builder> definitions() override
2760 return spl::make_shared<simple_definition_list_builder>(out);
2763 void example(const std::wstring& code, const std::wstring& caption) override
2765 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2767 if (!caption.empty())
2768 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2773 void begin_item(const std::wstring& name) override
2775 out << name << L"\n\n";
2779 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2781 std::wstringstream result;
2782 result << L"200 " << help_command << L" OK\r\n";
2783 max_width_sink width;
2784 ctx.help_repo->help(tags, width);
2785 short_description_sink sink(width.max_width, result);
2786 sink.width = width.max_width;
2787 ctx.help_repo->help(tags, sink);
2789 return result.str();
2792 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2794 std::wstringstream result;
2795 result << L"201 " << help_command << L" OK\r\n";
2796 auto joined = boost::join(ctx.parameters, L" ");
2797 long_description_sink sink(result);
2798 ctx.help_repo->help(tags, joined, sink);
2800 return result.str();
2803 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2805 sink.short_description(L"Show online help for AMCP commands.");
2806 sink.syntax(LR"(HELP {[command:string]})");
2807 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2808 sink.example(L">> HELP", L"Shows a list of commands.");
2809 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2812 std::wstring help_command(command_context& ctx)
2814 if (ctx.parameters.size() == 0)
2815 return create_help_list(L"HELP", ctx, { L"AMCP" });
2817 return create_help_details(L"HELP", ctx, { L"AMCP" });
2820 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2822 sink.short_description(L"Show online help for producers.");
2823 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2824 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2825 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2826 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2829 std::wstring help_producer_command(command_context& ctx)
2831 if (ctx.parameters.size() == 0)
2832 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2834 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2837 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2839 sink.short_description(L"Show online help for consumers.");
2840 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2841 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2842 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2843 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2846 std::wstring help_consumer_command(command_context& ctx)
2848 if (ctx.parameters.size() == 0)
2849 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2851 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2854 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2856 sink.short_description(L"Disconnect the session.");
2857 sink.syntax(L"BYE");
2859 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2860 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2863 std::wstring bye_command(command_context& ctx)
2865 ctx.client->disconnect();
2869 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2871 sink.short_description(L"Shutdown the server.");
2872 sink.syntax(L"KILL");
2873 sink.para()->text(L"Shuts the server down.");
2876 std::wstring kill_command(command_context& ctx)
2878 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2879 return L"202 KILL OK\r\n";
2882 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2884 sink.short_description(L"Shutdown the server with restart exit code.");
2885 sink.syntax(L"RESTART");
2887 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2888 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2891 std::wstring restart_command(command_context& ctx)
2893 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2894 return L"202 RESTART OK\r\n";
2897 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2899 sink.short_description(L"Lock or unlock access to a channel.");
2900 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2901 sink.para()->text(L"Allows for exclusive access to a channel.");
2902 sink.para()->text(L"Examples:");
2903 sink.example(L"LOCK 1 ACQUIRE secret");
2904 sink.example(L"LOCK 1 RELEASE");
2905 sink.example(L"LOCK 1 CLEAR");
2908 std::wstring lock_command(command_context& ctx)
2910 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2911 auto lock = ctx.channels.at(channel_index).lock;
2912 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2914 if (command == L"ACQUIRE")
2916 std::wstring lock_phrase = ctx.parameters.at(2);
2918 //TODO: read options
2920 //just lock one channel
2921 if (!lock->try_lock(lock_phrase, ctx.client))
2922 return L"503 LOCK ACQUIRE FAILED\r\n";
2924 return L"202 LOCK ACQUIRE OK\r\n";
2926 else if (command == L"RELEASE")
2928 lock->release_lock(ctx.client);
2929 return L"202 LOCK RELEASE OK\r\n";
2931 else if (command == L"CLEAR")
2933 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2934 std::wstring client_override_phrase;
2936 if (!override_phrase.empty())
2937 client_override_phrase = ctx.parameters.at(2);
2939 //just clear one channel
2940 if (client_override_phrase != override_phrase)
2941 return L"503 LOCK CLEAR FAILED\r\n";
2943 lock->clear_locks();
2945 return L"202 LOCK CLEAR OK\r\n";
2948 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2951 void register_commands(amcp_command_repository& repo)
2953 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2954 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2955 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2956 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2957 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2958 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2959 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2960 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2961 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2962 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2963 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2964 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2965 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2966 repo.register_command( L"Basic Commands", L"LOG CATEGORY", log_category_describer, log_category_command, 2);
2967 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2968 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2970 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2971 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2972 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2973 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2975 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2976 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2977 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2978 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2979 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2980 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2981 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2982 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2983 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2985 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2986 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2987 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2988 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2989 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2990 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2991 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2992 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2993 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2994 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2995 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2996 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2997 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2998 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2999 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
3000 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
3001 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
3002 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
3003 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
3004 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
3005 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
3006 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
3008 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
3009 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
3010 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
3011 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
3013 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
3014 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
3015 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
3016 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
3017 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
3018 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
3019 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
3020 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
3021 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
3022 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
3023 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
3024 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
3025 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
3026 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
3027 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
3028 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
3029 repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
3030 repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
3031 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
3032 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
3033 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
3034 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
3035 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
3036 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
3040 }} //namespace caspar