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 get_sub_directory(const std::wstring& base_folder, const std::wstring& sub_directory)
238 if (sub_directory.empty())
241 auto found = find_case_insensitive(base_folder + L"/" + sub_directory);
244 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Sub directory " + sub_directory + L" not found."));
249 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo, const std::wstring& sub_directory = L"")
251 std::wstringstream replyString;
252 for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::media_folder(), sub_directory)), end; itr != end; ++itr)
253 replyString << MediaInfo(itr->path(), media_info_repo);
255 return boost::to_upper_copy(replyString.str());
258 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry, const std::wstring& sub_directory = L"")
260 std::wstringstream replyString;
262 for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::template_folder(), sub_directory)), end; itr != end; ++itr)
264 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
266 auto relativePath = get_relative_without_extension(itr->path(), env::template_folder());
268 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
269 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
270 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
272 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
273 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
275 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
277 auto dir = relativePath.parent_path();
278 auto file = boost::to_upper_copy(relativePath.filename().wstring());
279 relativePath = dir / file;
281 auto str = relativePath.generic_wstring();
282 boost::trim_if(str, boost::is_any_of("\\/"));
284 auto template_type = cg_registry->get_cg_producer_name(str);
286 replyString << L"\"" << str
287 << L"\" " << sizeWStr
288 << L" " << writeTimeWStr
289 << L" " << template_type
293 return replyString.str();
296 std::vector<spl::shared_ptr<core::video_channel>> get_channels(const command_context& ctx)
298 return cpplinq::from(ctx.channels)
299 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
303 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
305 return core::frame_producer_dependencies(
306 channel->frame_factory(),
308 channel->video_format_desc(),
309 ctx.producer_registry);
314 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
316 sink.short_description(L"Load a media file or resource in the background.");
317 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]})");
319 ->text(L"Loads a producer in the background and prepares it for playout. ")
320 ->text(L"If no layer is specified the default layer index will be used.");
322 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
323 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
325 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
326 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
328 ->code(L"loop")->text(L" will cause the clip to loop.");
330 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
332 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
334 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
335 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
336 sink.para()->text(L"Examples:");
337 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
338 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
339 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
340 sink.example(L">> LOADBG 1-0 MY_FILE");
342 L">> PLAY 1-1 MY_FILE\n"
343 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
344 L"To automatically fade out a layer after a video file has been played to the end");
346 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
348 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
349 ->code(L"filter")->text(L" command.");
352 std::wstring loadbg_command(command_context& ctx)
354 transition_info transitionInfo;
358 std::wstring message;
359 for (size_t n = 0; n < ctx.parameters.size(); ++n)
360 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
362 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)?.*)");
364 if (boost::regex_match(message, what, expr))
366 auto transition = what["TRANSITION"].str();
367 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
368 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
369 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
370 transitionInfo.tweener = tween;
372 if (transition == L"CUT")
373 transitionInfo.type = transition_type::cut;
374 else if (transition == L"MIX")
375 transitionInfo.type = transition_type::mix;
376 else if (transition == L"PUSH")
377 transitionInfo.type = transition_type::push;
378 else if (transition == L"SLIDE")
379 transitionInfo.type = transition_type::slide;
380 else if (transition == L"WIPE")
381 transitionInfo.type = transition_type::wipe;
383 if (direction == L"FROMLEFT")
384 transitionInfo.direction = transition_direction::from_left;
385 else if (direction == L"FROMRIGHT")
386 transitionInfo.direction = transition_direction::from_right;
387 else if (direction == L"LEFT")
388 transitionInfo.direction = transition_direction::from_right;
389 else if (direction == L"RIGHT")
390 transitionInfo.direction = transition_direction::from_left;
393 //Perform loading of the clip
394 core::diagnostics::scoped_call_context save;
395 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
396 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
398 auto channel = ctx.channel.channel;
399 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
401 if (pFP == frame_producer::empty())
402 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
404 bool auto_play = contains_param(L"AUTO", ctx.parameters);
406 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
408 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
410 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
412 return L"202 LOADBG OK\r\n";
415 void load_describer(core::help_sink& sink, const core::help_repository& repo)
417 sink.short_description(L"Load a media file or resource to the foreground.");
418 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
420 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
421 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
422 sink.para()->text(L"Examples:");
423 sink.example(L">> LOAD 1 MY_FILE");
424 sink.example(L">> LOAD 1-1 MY_FILE");
425 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
428 std::wstring load_command(command_context& ctx)
430 core::diagnostics::scoped_call_context save;
431 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
432 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
433 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
434 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
436 return L"202 LOAD OK\r\n";
439 void play_describer(core::help_sink& sink, const core::help_repository& repository)
441 sink.short_description(L"Play a media file or resource.");
442 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
444 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
445 ->text(L") is prepared, it will be executed.");
447 ->text(L"If additional parameters (see ")->see(L"LOADBG")
448 ->text(L") are provided then the provided clip will first be loaded to the background.");
449 sink.para()->text(L"Examples:");
450 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
451 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
452 sink.example(L">> PLAY 1-0 MY_FILE");
453 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
456 std::wstring play_command(command_context& ctx)
458 if (!ctx.parameters.empty())
461 ctx.channel.channel->stage().play(ctx.layer_index());
463 return L"202 PLAY OK\r\n";
466 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
468 sink.short_description(L"Pause playback of a layer.");
469 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
471 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
472 ->text(L" command can be used to resume playback again.");
473 sink.para()->text(L"Examples:");
474 sink.example(L">> PAUSE 1");
475 sink.example(L">> PAUSE 1-1");
478 std::wstring pause_command(command_context& ctx)
480 ctx.channel.channel->stage().pause(ctx.layer_index());
481 return L"202 PAUSE OK\r\n";
484 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
486 sink.short_description(L"Resume playback of a layer.");
487 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
489 ->text(L"Resumes playback of a foreground clip previously paused with the ")
490 ->see(L"PAUSE")->text(L" command.");
491 sink.para()->text(L"Examples:");
492 sink.example(L">> RESUME 1");
493 sink.example(L">> RESUME 1-1");
496 std::wstring resume_command(command_context& ctx)
498 ctx.channel.channel->stage().resume(ctx.layer_index());
499 return L"202 RESUME OK\r\n";
502 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
504 sink.short_description(L"Remove the foreground clip of a layer.");
505 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
507 ->text(L"Removes the foreground clip of the specified layer.");
508 sink.para()->text(L"Examples:");
509 sink.example(L">> STOP 1");
510 sink.example(L">> STOP 1-1");
513 std::wstring stop_command(command_context& ctx)
515 ctx.channel.channel->stage().stop(ctx.layer_index());
516 return L"202 STOP OK\r\n";
519 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
521 sink.short_description(L"Remove all clips of a layer or an entire channel.");
522 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
524 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
525 ->text(L"If no layer is specified then all layers in the specified ")
526 ->code(L"video_channel")->text(L" are cleared.");
527 sink.para()->text(L"Examples:");
528 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
529 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
532 std::wstring clear_command(command_context& ctx)
534 int index = ctx.layer_index(std::numeric_limits<int>::min());
535 if (index != std::numeric_limits<int>::min())
536 ctx.channel.channel->stage().clear(index);
538 ctx.channel.channel->stage().clear();
540 return L"202 CLEAR OK\r\n";
543 void call_describer(core::help_sink& sink, const core::help_repository& repo)
545 sink.short_description(L"Call a method on a producer.");
546 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
548 ->text(L"Calls method on the specified producer with the provided ")
549 ->code(L"param")->text(L" string.");
550 sink.para()->text(L"Examples:");
551 sink.example(L">> CALL 1 LOOP");
552 sink.example(L">> CALL 1-2 SEEK 25");
555 std::wstring call_command(command_context& ctx)
557 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters).get();
559 // TODO: because of std::async deferred timed waiting does not work
561 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
562 if (wait_res == std::future_status::timeout)
563 CASPAR_THROW_EXCEPTION(timed_out());*/
565 std::wstringstream replyString;
567 replyString << L"202 CALL OK\r\n";
569 replyString << L"201 CALL OK\r\n" << result << L"\r\n";
571 return replyString.str();
574 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
576 sink.short_description(L"Swap layers between channels.");
577 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
579 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
580 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
581 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
582 sink.para()->text(L"Examples:");
583 sink.example(L">> SWAP 1 2");
584 sink.example(L">> SWAP 1-1 2-3");
585 sink.example(L">> SWAP 1-1 1-2");
586 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
589 std::wstring swap_command(command_context& ctx)
591 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
593 if (ctx.layer_index(-1) != -1)
595 std::vector<std::string> strs;
596 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
598 auto ch1 = ctx.channel.channel;
599 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
601 int l1 = ctx.layer_index();
602 int l2 = boost::lexical_cast<int>(strs.at(1));
604 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
608 auto ch1 = ctx.channel.channel;
609 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
610 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
613 return L"202 SWAP OK\r\n";
616 void add_describer(core::help_sink& sink, const core::help_repository& repo)
618 sink.short_description(L"Add a consumer to a video channel.");
619 sink.syntax(L"ADD [video_channel:int]{-[consumer_index:int]} [consumer:string] [parameters:string]");
621 ->text(L"Adds a consumer to the specified video channel. The string ")
622 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
623 ->text(L"If a successful match is found a consumer will be created and added to the ")
624 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
625 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
626 ->see(L"the CasparCG config file")->text(L".");
628 ->text(L"Specifying ")->code(L"consumer_index")
629 ->text(L" overrides the index that the consumer itself decides and can later be used with the ")
630 ->see(L"REMOVE")->text(L" command to remove the consumer.");
631 sink.para()->text(L"Examples:");
632 sink.example(L">> ADD 1 DECKLINK 1");
633 sink.example(L">> ADD 1 BLUEFISH 2");
634 sink.example(L">> ADD 1 SCREEN");
635 sink.example(L">> ADD 1 AUDIO");
636 sink.example(L">> ADD 1 IMAGE filename");
637 sink.example(L">> ADD 2 SYNCTO 1");
638 sink.example(L">> ADD 1 FILE filename.mov");
639 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
641 L">> ADD 1-700 FILE filename.mov SEPARATE_KEY\n"
642 L">> REMOVE 1-700", L"overriding the consumer index to easier remove later.");
643 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
644 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
647 std::wstring add_command(command_context& ctx)
649 replace_placeholders(
650 L"<CLIENT_IP_ADDRESS>",
651 ctx.client->address(),
654 core::diagnostics::scoped_call_context save;
655 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
657 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx));
658 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
660 return L"202 ADD OK\r\n";
663 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
665 sink.short_description(L"Remove a consumer from a video channel.");
666 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
668 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
669 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
670 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
671 sink.para()->text(L"Examples:");
672 sink.example(L">> REMOVE 1 DECKLINK 1");
673 sink.example(L">> REMOVE 1 BLUEFISH 2");
674 sink.example(L">> REMOVE 1 SCREEN");
675 sink.example(L">> REMOVE 1 AUDIO");
676 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
679 std::wstring remove_command(command_context& ctx)
681 auto index = ctx.layer_index(std::numeric_limits<int>::min());
683 if (index == std::numeric_limits<int>::min())
685 replace_placeholders(
686 L"<CLIENT_IP_ADDRESS>",
687 ctx.client->address(),
690 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx))->index();
693 ctx.channel.channel->output().remove(index);
695 return L"202 REMOVE OK\r\n";
698 void print_describer(core::help_sink& sink, const core::help_repository& repo)
700 sink.short_description(L"Take a snapshot of a channel.");
701 sink.syntax(L"PRINT [video_channel:int]");
703 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
704 ->code(L"media")->text(L" folder.");
705 sink.para()->text(L"Examples:");
706 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
709 std::wstring print_command(command_context& ctx)
711 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage(), get_channels(ctx)));
713 return L"202 PRINT OK\r\n";
716 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
718 sink.short_description(L"Change the log level of the server.");
719 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
720 sink.para()->text(L"Changes the log level of the server.");
721 sink.para()->text(L"Examples:");
722 sink.example(L">> LOG LEVEL trace");
723 sink.example(L">> LOG LEVEL info");
726 std::wstring log_level_command(command_context& ctx)
728 log::set_log_level(ctx.parameters.at(0));
730 return L"202 LOG OK\r\n";
733 void log_category_describer(core::help_sink& sink, const core::help_repository& repo)
735 sink.short_description(L"Enable/disable a logging category in the server.");
736 sink.syntax(L"LOG CATEGORY [category:calltrace,communication] [enable:0,1]");
737 sink.para()->text(L"Enables or disables the specified logging category.");
738 sink.para()->text(L"Examples:");
739 sink.example(L">> LOG CATEGORY calltrace 1", L"to enable call trace");
740 sink.example(L">> LOG CATEGORY calltrace 0", L"to disable call trace");
743 std::wstring log_category_command(command_context& ctx)
745 log::set_log_category(ctx.parameters.at(0), ctx.parameters.at(1) == L"1");
747 return L"202 LOG OK\r\n";
750 void set_describer(core::help_sink& sink, const core::help_repository& repo)
752 sink.short_description(L"Change the value of a channel variable.");
753 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
754 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
756 ->item(L"MODE", L"Changes the video format of the channel.")
757 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
758 sink.para()->text(L"Examples:");
759 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
760 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
763 std::wstring set_command(command_context& ctx)
765 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
766 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
770 auto format_desc = core::video_format_desc(value);
771 if (format_desc.format != core::video_format::invalid)
773 ctx.channel.channel->video_format_desc(format_desc);
774 return L"202 SET MODE OK\r\n";
777 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video mode"));
779 else if (name == L"CHANNEL_LAYOUT")
781 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
785 ctx.channel.channel->audio_channel_layout(*channel_layout);
786 return L"202 SET CHANNEL_LAYOUT OK\r\n";
789 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid audio channel layout"));
792 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid channel variable"));
795 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
797 sink.short_description(L"Store a dataset.");
798 sink.syntax(L"DATA STORE [name:string] [data:string]");
799 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
800 sink.para()->text(L"Directories will be created if they do not exist.");
801 sink.para()->text(L"Examples:");
802 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
803 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
806 std::wstring data_store_command(command_context& ctx)
808 std::wstring filename = env::data_folder();
809 filename.append(ctx.parameters[0]);
810 filename.append(L".ftd");
812 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
813 auto found_data_path = find_case_insensitive(data_path);
816 data_path = *found_data_path;
818 if (!boost::filesystem::exists(data_path))
819 boost::filesystem::create_directories(data_path);
821 auto found_filename = find_case_insensitive(filename);
824 filename = *found_filename; // Overwrite case insensitive.
826 boost::filesystem::wofstream datafile(filename);
828 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
830 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
831 datafile << ctx.parameters[1] << std::flush;
834 return L"202 DATA STORE OK\r\n";
837 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
839 sink.short_description(L"Retrieve a dataset.");
840 sink.syntax(L"DATA RETRIEVE [name:string]");
841 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
842 sink.para()->text(L"Examples:");
843 sink.example(L">> DATA RETRIEVE my_data");
844 sink.example(L">> DATA RETRIEVE Folder1/my_data");
847 std::wstring data_retrieve_command(command_context& ctx)
849 std::wstring filename = env::data_folder();
850 filename.append(ctx.parameters[0]);
851 filename.append(L".ftd");
853 std::wstring file_contents;
855 auto found_file = find_case_insensitive(filename);
858 file_contents = read_file(boost::filesystem::path(*found_file));
860 if (file_contents.empty())
861 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
863 std::wstringstream reply;
864 reply << L"201 DATA RETRIEVE OK\r\n";
866 std::wstringstream file_contents_stream(file_contents);
869 bool firstLine = true;
870 while (std::getline(file_contents_stream, line))
884 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
886 sink.short_description(L"List stored datasets.");
887 sink.syntax(L"DATA LIST {[sub_directory:string]}");
888 sink.para()->text(L"Returns a list of stored datasets.");
890 ->text(L"if the optional ")->code(L"sub_directory")
891 ->text(L" is specified only the datasets in that sub directory will be returned.");
894 std::wstring data_list_command(command_context& ctx)
896 std::wstring sub_directory;
898 if (!ctx.parameters.empty())
899 sub_directory = ctx.parameters.at(0);
901 std::wstringstream replyString;
902 replyString << L"200 DATA LIST OK\r\n";
904 for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::data_folder(), sub_directory)), end; itr != end; ++itr)
906 if (boost::filesystem::is_regular_file(itr->path()))
908 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
911 auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
912 auto str = relativePath.generic_wstring();
914 if (str[0] == L'\\' || str[0] == L'/')
915 str = std::wstring(str.begin() + 1, str.end());
917 replyString << str << L"\r\n";
921 replyString << L"\r\n";
923 return boost::to_upper_copy(replyString.str());
926 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
928 sink.short_description(L"Remove a stored dataset.");
929 sink.syntax(L"DATA REMOVE [name:string]");
930 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
931 sink.para()->text(L"Examples:");
932 sink.example(L">> DATA REMOVE my_data");
933 sink.example(L">> DATA REMOVE Folder1/my_data");
936 std::wstring data_remove_command(command_context& ctx)
938 std::wstring filename = env::data_folder();
939 filename.append(ctx.parameters[0]);
940 filename.append(L".ftd");
942 if (!boost::filesystem::exists(filename))
943 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
945 if (!boost::filesystem::remove(filename))
946 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
948 return L"202 DATA REMOVE OK\r\n";
951 // Template Graphics Commands
953 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
955 sink.short_description(L"Prepare a template for displaying.");
956 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
958 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
959 ->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.");
960 sink.para()->text(L"Examples:");
961 sink.example(L"CG 1 ADD 10 svtnews/info 1");
964 std::wstring cg_add_command(command_context& ctx)
966 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
968 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
969 std::wstring label; //_parameters[2]
970 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
971 unsigned int dataIndex = 3;
973 if (ctx.parameters.at(2).length() > 1)
975 label = ctx.parameters.at(2);
978 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
979 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
982 { //read play-on-load-flag
983 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
986 const wchar_t* pDataString = 0;
987 std::wstring dataFromFile;
988 if (ctx.parameters.size() > dataIndex)
990 const std::wstring& dataString = ctx.parameters.at(dataIndex);
992 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
993 pDataString = dataString.c_str();
996 //The data is not an XML-string, it must be a filename
997 std::wstring filename = env::data_folder();
998 filename.append(dataString);
999 filename.append(L".ftd");
1001 auto found_file = find_case_insensitive(filename);
1005 dataFromFile = read_file(boost::filesystem::path(*found_file));
1006 pDataString = dataFromFile.c_str();
1011 auto filename = ctx.parameters.at(1);
1012 auto proxy = ctx.cg_registry->get_or_create_proxy(
1013 spl::make_shared_ptr(ctx.channel.channel),
1014 get_producer_dependencies(ctx.channel.channel, ctx),
1015 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
1018 if (proxy == core::cg_proxy::empty())
1019 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
1021 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
1023 return L"202 CG OK\r\n";
1026 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
1028 sink.short_description(L"Play and display a template.");
1029 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
1030 sink.para()->text(L"Plays and displays the template in the specified layer.");
1031 sink.para()->text(L"Examples:");
1032 sink.example(L"CG 1 PLAY 0");
1035 std::wstring cg_play_command(command_context& ctx)
1037 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1038 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1040 return L"202 CG OK\r\n";
1043 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1045 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1047 if (proxy == cg_proxy::empty())
1048 CASPAR_THROW_EXCEPTION(expected_user_error() << msg_info(L"No CG proxy running on layer"));
1053 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1055 sink.short_description(L"Stop and remove a template.");
1056 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1058 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1059 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1060 sink.para()->text(L"Examples:");
1061 sink.example(L"CG 1 STOP 0");
1064 std::wstring cg_stop_command(command_context& ctx)
1066 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1067 get_expected_cg_proxy(ctx)->stop(layer, 0);
1069 return L"202 CG OK\r\n";
1072 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1074 sink.short_description(LR"(Trigger a "continue" in a template.)");
1075 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1077 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1078 ->text(L"This is used to control animations that has multiple discreet steps.");
1079 sink.para()->text(L"Examples:");
1080 sink.example(L"CG 1 NEXT 0");
1083 std::wstring cg_next_command(command_context& ctx)
1085 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1086 get_expected_cg_proxy(ctx)->next(layer);
1088 return L"202 CG OK\r\n";
1091 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1093 sink.short_description(L"Remove a template.");
1094 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1095 sink.para()->text(L"Removes the template from the specified layer.");
1096 sink.para()->text(L"Examples:");
1097 sink.example(L"CG 1 REMOVE 0");
1100 std::wstring cg_remove_command(command_context& ctx)
1102 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1103 get_expected_cg_proxy(ctx)->remove(layer);
1105 return L"202 CG OK\r\n";
1108 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1110 sink.short_description(L"Remove all templates on a video layer.");
1111 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1112 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1113 sink.para()->text(L"Examples:");
1114 sink.example(L"CG 1 CLEAR");
1117 std::wstring cg_clear_command(command_context& ctx)
1119 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1121 return L"202 CG OK\r\n";
1124 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1126 sink.short_description(L"Update a template with new data.");
1127 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1128 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.");
1131 std::wstring cg_update_command(command_context& ctx)
1133 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1135 std::wstring dataString = ctx.parameters.at(1);
1136 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1138 //The data is not XML or Json, it must be a filename
1139 std::wstring filename = env::data_folder();
1140 filename.append(dataString);
1141 filename.append(L".ftd");
1143 dataString = read_file(boost::filesystem::path(filename));
1146 get_expected_cg_proxy(ctx)->update(layer, dataString);
1148 return L"202 CG OK\r\n";
1151 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1153 sink.short_description(L"Invoke a method/label on a template.");
1154 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1155 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1156 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1159 std::wstring cg_invoke_command(command_context& ctx)
1161 std::wstringstream replyString;
1162 replyString << L"201 CG OK\r\n";
1163 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1164 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1165 replyString << result << L"\r\n";
1167 return replyString.str();
1170 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1172 sink.short_description(L"Get information about a running template or the template host.");
1173 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1174 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1175 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1178 std::wstring cg_info_command(command_context& ctx)
1180 std::wstringstream replyString;
1181 replyString << L"201 CG OK\r\n";
1183 if (ctx.parameters.empty())
1185 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1186 replyString << info << L"\r\n";
1190 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1191 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1193 replyString << desc << L"\r\n";
1196 return replyString.str();
1201 core::frame_transform get_current_transform(command_context& ctx)
1203 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1206 template<typename Func>
1207 std::wstring reply_value(command_context& ctx, const Func& extractor)
1209 auto value = extractor(get_current_transform(ctx));
1211 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1214 class transforms_applier
1216 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1218 std::vector<stage::transform_tuple_t> transforms_;
1219 command_context& ctx_;
1222 transforms_applier(command_context& ctx)
1225 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1228 ctx.parameters.pop_back();
1231 void add(stage::transform_tuple_t&& transform)
1233 transforms_.push_back(std::move(transform));
1236 void commit_deferred()
1238 auto& transforms = deferred_transforms_[ctx_.channel_index];
1239 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1247 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1248 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1251 ctx_.channel.channel->stage().apply_transforms(transforms_);
1254 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1256 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1258 sink.short_description(L"Let a layer act as alpha for the one obove.");
1259 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1261 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1262 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1263 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1264 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1265 ->text(L"instead it will be used as the key for the layer above.");
1266 sink.para()->text(L"Examples:");
1267 sink.example(L">> MIXER 1-0 KEYER 1");
1269 L">> MIXER 1-0 KEYER\n"
1270 L"<< 201 MIXER OK\n"
1271 L"<< 1", L"to retrieve the current state");
1274 std::wstring mixer_keyer_command(command_context& ctx)
1276 if (ctx.parameters.empty())
1277 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1279 transforms_applier transforms(ctx);
1280 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1281 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1283 transform.image_transform.is_key = value;
1285 }, 0, tweener(L"linear")));
1288 return L"202 MIXER OK\r\n";
1291 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1293 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1295 sink.short_description(L"Enable chroma keying on a layer.");
1296 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);
1298 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1299 sink.para()->text(L"The chroma keying is done in the HSB/HSV color space.");
1300 sink.para()->text(L"Parameters:");
1302 ->item(L"enable", L"0 to disable chroma keying on layer. The rest of the parameters should not be given when disabling.")
1303 ->item(L"target_hue", L"The hue in degrees between 0-360 where the center of the hue window will open up.")
1304 ->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.")
1305 ->item(L"min_saturation", L"The minimum saturation within 0.0-1.0 required for a color to be within the chroma window.")
1306 ->item(L"min_brightness", L"The minimum brightness within 0.0-1.0 required for a color to be within the chroma window.")
1307 ->item(L"softness", L"The softness of the chroma keying window.")
1308 ->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.")
1309 ->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.")
1310 ->item(L"show_mask", L"If enabled, only shows the mask. Useful while editing the chroma key settings.")
1312 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");
1313 sink.example(L">> MIXER 1-1 CHROMA 0", L"for disabling chroma keying");
1315 L">> MIXER 1-1 CHROMA 0\n"
1316 L"<< 202 MIXER OK\n"
1317 L"<< 1 120 0.1 0 0 0.1 1 2 0", L"for getting the current chroma key mode");
1318 sink.para()->text(L"Deprecated legacy syntax:");
1319 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] [spill:float]}}" + ANIMATION_SYNTAX);
1320 sink.para()->text(L"Deprecated legacy examples:");
1321 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 1.0 25 easeinsine");
1322 sink.example(L">> MIXER 1-1 CHROMA none");
1325 std::wstring mixer_chroma_command(command_context& ctx)
1327 if (ctx.parameters.empty())
1329 auto chroma = get_current_transform(ctx).image_transform.chroma;
1330 return L"201 MIXER OK\r\n"
1331 + std::wstring(chroma.enable ? L"1 " : L"0 ")
1332 + boost::lexical_cast<std::wstring>(chroma.target_hue) + L" "
1333 + boost::lexical_cast<std::wstring>(chroma.hue_width) + L" "
1334 + boost::lexical_cast<std::wstring>(chroma.min_saturation) + L" "
1335 + boost::lexical_cast<std::wstring>(chroma.min_brightness) + L" "
1336 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1337 + boost::lexical_cast<std::wstring>(chroma.spill) + L" "
1338 + boost::lexical_cast<std::wstring>(chroma.spill_darken) + L" "
1339 + std::wstring(chroma.show_mask ? L"1" : L"0") + L"\r\n";
1342 transforms_applier transforms(ctx);
1343 core::chroma chroma;
1348 auto legacy_mode = core::get_chroma_mode(ctx.parameters.at(0));
1353 duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1354 tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1356 if (*legacy_mode == chroma::legacy_type::none)
1358 chroma.enable = false;
1362 chroma.enable = true;
1363 chroma.hue_width = 0.5 - boost::lexical_cast<double>(ctx.parameters.at(1)) * 0.5;
1364 chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(1));
1365 chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(1));
1366 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2)) - boost::lexical_cast<double>(ctx.parameters.at(1));
1367 chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(3));
1368 chroma.spill_darken = 2;
1370 if (*legacy_mode == chroma::legacy_type::green)
1371 chroma.target_hue = 120;
1372 else if (*legacy_mode == chroma::legacy_type::blue)
1373 chroma.target_hue = 240;
1378 duration = ctx.parameters.size() > 9 ? boost::lexical_cast<int>(ctx.parameters.at(9)) : 0;
1379 tween = ctx.parameters.size() > 10 ? ctx.parameters.at(10) : L"linear";
1381 chroma.enable = ctx.parameters.at(0) == L"1";
1385 chroma.target_hue = boost::lexical_cast<double>(ctx.parameters.at(1));
1386 chroma.hue_width = boost::lexical_cast<double>(ctx.parameters.at(2));
1387 chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(3));
1388 chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(4));
1389 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(5));
1390 chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(6));
1391 chroma.spill_darken = boost::lexical_cast<double>(ctx.parameters.at(7));
1392 chroma.show_mask = boost::lexical_cast<double>(ctx.parameters.at(8));
1397 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1399 transform.image_transform.chroma = chroma;
1401 }, duration, tween));
1404 return L"202 MIXER OK\r\n";
1407 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1409 sink.short_description(L"Set the blend mode for a layer.");
1410 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1412 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1413 ->text(L"If no argument is given the current blend mode is returned.");
1415 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1416 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1417 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1418 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1419 sink.para()->text(L"Examples:");
1420 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1422 L">> MIXER 1-1 BLEND\n"
1423 L"<< 201 MIXER OK\n"
1424 L"<< SCREEN", L"for getting the current blend mode");
1425 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1428 std::wstring mixer_blend_command(command_context& ctx)
1430 if (ctx.parameters.empty())
1431 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1433 transforms_applier transforms(ctx);
1434 auto value = get_blend_mode(ctx.parameters.at(0));
1435 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1437 transform.image_transform.blend_mode = value;
1439 }, 0, tweener(L"linear")));
1442 return L"202 MIXER OK\r\n";
1445 template<typename Getter, typename Setter>
1446 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1448 if (ctx.parameters.empty())
1449 return reply_value(ctx, getter);
1451 transforms_applier transforms(ctx);
1452 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1453 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1454 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1456 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1458 setter(transform, value);
1460 }, duration, tween));
1463 return L"202 MIXER OK\r\n";
1466 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1468 sink.short_description(L"Change the opacity of a layer.");
1469 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1470 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1471 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1472 sink.para()->text(L"Examples:");
1473 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1475 L">> MIXER 1-0 OPACITY\n"
1476 L"<< 201 MIXER OK\n"
1477 L"<< 0.5", L"to retrieve the current opacity");
1480 std::wstring mixer_opacity_command(command_context& ctx)
1482 return single_double_animatable_mixer_command(
1484 [](const frame_transform& t) { return t.image_transform.opacity; },
1485 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1488 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1490 sink.short_description(L"Change the brightness of a layer.");
1491 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1492 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1493 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1494 sink.para()->text(L"Examples:");
1495 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1497 L">> MIXER 1-0 BRIGHTNESS\n"
1498 L"<< 201 MIXER OK\n"
1499 L"0.5", L"to retrieve the current brightness");
1502 std::wstring mixer_brightness_command(command_context& ctx)
1504 return single_double_animatable_mixer_command(
1506 [](const frame_transform& t) { return t.image_transform.brightness; },
1507 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1510 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1512 sink.short_description(L"Change the saturation of a layer.");
1513 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1514 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1515 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1516 sink.para()->text(L"Examples:");
1517 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1519 L">> MIXER 1-0 SATURATION\n"
1520 L"<< 201 MIXER OK\n"
1521 L"<< 0.5", L"to retrieve the current saturation");
1524 std::wstring mixer_saturation_command(command_context& ctx)
1526 return single_double_animatable_mixer_command(
1528 [](const frame_transform& t) { return t.image_transform.saturation; },
1529 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1532 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1534 sink.short_description(L"Change the contrast of a layer.");
1535 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1536 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1537 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1538 sink.para()->text(L"Examples:");
1539 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1541 L">> MIXER 1-0 CONTRAST\n"
1542 L"<< 201 MIXER OK\n"
1543 L"<< 0.5", L"to retrieve the current contrast");
1546 std::wstring mixer_contrast_command(command_context& ctx)
1548 return single_double_animatable_mixer_command(
1550 [](const frame_transform& t) { return t.image_transform.contrast; },
1551 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1554 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1556 sink.short_description(L"Adjust the video levels of a layer.");
1557 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);
1559 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1561 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1562 ->item(L"gamma", L"Adjusts the gamma of the image.")
1563 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1564 sink.para()->text(L"Examples:");
1565 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");
1566 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");
1567 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1569 L">> MIXER 1-10 LEVELS\n"
1570 L"<< 201 MIXER OK\n"
1571 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1574 std::wstring mixer_levels_command(command_context& ctx)
1576 if (ctx.parameters.empty())
1578 auto levels = get_current_transform(ctx).image_transform.levels;
1579 return L"201 MIXER OK\r\n"
1580 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1581 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1582 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1583 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1584 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1587 transforms_applier transforms(ctx);
1589 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1590 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1591 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1592 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1593 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1594 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1595 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1597 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1599 transform.image_transform.levels = value;
1601 }, duration, tween));
1604 return L"202 MIXER OK\r\n";
1607 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1609 sink.short_description(L"Change the fill position and scale of a layer.");
1610 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1612 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1613 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1614 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1615 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1617 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1618 ->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.");
1619 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1621 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1622 ->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, ")
1623 ->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");
1625 ->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.")
1626 ->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.")
1627 ->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.")
1628 ->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.");
1629 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1630 sink.para()->text(L"Examples:");
1631 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1633 L">> MIXER 1-0 FILL\n"
1634 L"<< 201 MIXER OK\n"
1635 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1638 std::wstring mixer_fill_command(command_context& ctx)
1640 if (ctx.parameters.empty())
1642 auto transform = get_current_transform(ctx).image_transform;
1643 auto translation = transform.fill_translation;
1644 auto scale = transform.fill_scale;
1645 return L"201 MIXER OK\r\n"
1646 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1647 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1648 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1649 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1652 transforms_applier transforms(ctx);
1653 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1654 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1655 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1656 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1657 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1658 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1660 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1662 transform.image_transform.fill_translation[0] = x;
1663 transform.image_transform.fill_translation[1] = y;
1664 transform.image_transform.fill_scale[0] = x_s;
1665 transform.image_transform.fill_scale[1] = y_s;
1667 }, duration, tween));
1670 return L"202 MIXER OK\r\n";
1673 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1675 sink.short_description(L"Change the clipping viewport of a layer.");
1676 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1678 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1679 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1680 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1682 ->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.")
1683 ->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.")
1684 ->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.")
1685 ->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.");
1686 sink.para()->text(L"Examples:");
1687 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1689 L">> MIXER 1-0 CLIP\n"
1690 L"<< 201 MIXER OK\n"
1691 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1694 std::wstring mixer_clip_command(command_context& ctx)
1696 if (ctx.parameters.empty())
1698 auto transform = get_current_transform(ctx).image_transform;
1699 auto translation = transform.clip_translation;
1700 auto scale = transform.clip_scale;
1702 return L"201 MIXER OK\r\n"
1703 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1704 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1705 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1706 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1709 transforms_applier transforms(ctx);
1710 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1711 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1712 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1713 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1714 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1715 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1717 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1719 transform.image_transform.clip_translation[0] = x;
1720 transform.image_transform.clip_translation[1] = y;
1721 transform.image_transform.clip_scale[0] = x_s;
1722 transform.image_transform.clip_scale[1] = y_s;
1724 }, duration, tween));
1727 return L"202 MIXER OK\r\n";
1730 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1732 sink.short_description(L"Change the anchor point of a layer.");
1733 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1734 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1736 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1737 ->text(L" will be done from.");
1739 ->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.")
1740 ->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.");
1741 sink.para()->text(L"Examples:");
1742 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1744 L">> MIXER 1-10 ANCHOR\n"
1745 L"<< 201 MIXER OK\n"
1746 L"<< 0.5 0.6", L"gets the anchor point");
1749 std::wstring mixer_anchor_command(command_context& ctx)
1751 if (ctx.parameters.empty())
1753 auto transform = get_current_transform(ctx).image_transform;
1754 auto anchor = transform.anchor;
1755 return L"201 MIXER OK\r\n"
1756 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1757 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1760 transforms_applier transforms(ctx);
1761 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1762 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1763 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1764 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1766 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1768 transform.image_transform.anchor[0] = x;
1769 transform.image_transform.anchor[1] = y;
1771 }, duration, tween));
1774 return L"202 MIXER OK\r\n";
1777 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1779 sink.short_description(L"Crop a layer.");
1780 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);
1782 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1783 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1784 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1786 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1787 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1788 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1789 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1790 sink.para()->text(L"Examples:");
1791 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");
1793 L">> MIXER 1-0 CROP\n"
1794 L"<< 201 MIXER OK\n"
1795 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1798 std::wstring mixer_crop_command(command_context& ctx)
1800 if (ctx.parameters.empty())
1802 auto crop = get_current_transform(ctx).image_transform.crop;
1803 return L"201 MIXER OK\r\n"
1804 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1805 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1806 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1807 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1810 transforms_applier transforms(ctx);
1811 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1812 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1813 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1814 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1815 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1816 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1818 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1820 transform.image_transform.crop.ul[0] = ul_x;
1821 transform.image_transform.crop.ul[1] = ul_y;
1822 transform.image_transform.crop.lr[0] = lr_x;
1823 transform.image_transform.crop.lr[1] = lr_y;
1825 }, duration, tween));
1828 return L"202 MIXER OK\r\n";
1831 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1833 sink.short_description(L"Rotate a layer.");
1834 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1836 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1837 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1838 sink.para()->text(L"Examples:");
1839 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1841 L">> MIXER 1-0 ROTATION\n"
1842 L"<< 201 MIXER OK\n"
1843 L"<< 45", L"to retrieve the current angle");
1846 std::wstring mixer_rotation_command(command_context& ctx)
1848 static const double PI = 3.141592653589793;
1850 return single_double_animatable_mixer_command(
1852 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1853 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1856 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1858 sink.short_description(L"Adjust the perspective transform of a layer.");
1859 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);
1861 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1863 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1864 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1865 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1866 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1867 sink.para()->text(L"Examples:");
1868 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1870 L">> MIXER 1-10 PERSPECTIVE\n"
1871 L"<< 201 MIXER OK\n"
1872 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1875 std::wstring mixer_perspective_command(command_context& ctx)
1877 if (ctx.parameters.empty())
1879 auto perspective = get_current_transform(ctx).image_transform.perspective;
1882 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1883 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1884 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1885 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1886 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1887 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1888 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1889 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1892 transforms_applier transforms(ctx);
1893 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1894 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1895 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1896 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1897 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1898 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1899 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1900 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1901 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1902 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1904 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1906 transform.image_transform.perspective.ul[0] = ul_x;
1907 transform.image_transform.perspective.ul[1] = ul_y;
1908 transform.image_transform.perspective.ur[0] = ur_x;
1909 transform.image_transform.perspective.ur[1] = ur_y;
1910 transform.image_transform.perspective.lr[0] = lr_x;
1911 transform.image_transform.perspective.lr[1] = lr_y;
1912 transform.image_transform.perspective.ll[0] = ll_x;
1913 transform.image_transform.perspective.ll[1] = ll_y;
1915 }, duration, tween));
1918 return L"202 MIXER OK\r\n";
1921 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1923 sink.short_description(L"Enable or disable mipmapping for a layer.");
1924 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1926 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1927 ->text(L"If no argument is given the current state is returned.");
1928 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1929 sink.para()->text(L"Examples:");
1930 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1932 L">> MIXER 1-10 MIPMAP\n"
1933 L"<< 201 MIXER OK\n"
1934 L"<< 1", L"for getting the current state");
1937 std::wstring mixer_mipmap_command(command_context& ctx)
1939 if (ctx.parameters.empty())
1940 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1942 transforms_applier transforms(ctx);
1943 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1944 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1946 transform.image_transform.use_mipmap = value;
1948 }, 0, tweener(L"linear")));
1951 return L"202 MIXER OK\r\n";
1954 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1956 sink.short_description(L"Change the volume of a layer.");
1957 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1958 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1959 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1960 sink.para()->text(L"Examples:");
1961 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1962 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1964 L">> MIXER 1-0 VOLUME\n"
1965 L"<< 201 MIXER OK\n"
1966 L"<< 0.8", L"to retrieve the current volume");
1969 std::wstring mixer_volume_command(command_context& ctx)
1971 return single_double_animatable_mixer_command(
1973 [](const frame_transform& t) { return t.audio_transform.volume; },
1974 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1977 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1979 sink.short_description(L"Change the volume of an entire channel.");
1980 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1981 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1982 sink.para()->text(L"Examples:");
1983 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1984 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1985 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1988 std::wstring mixer_mastervolume_command(command_context& ctx)
1990 if (ctx.parameters.empty())
1992 auto volume = ctx.channel.channel->mixer().get_master_volume();
1993 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1996 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1997 ctx.channel.channel->mixer().set_master_volume(master_volume);
1999 return L"202 MIXER OK\r\n";
2002 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
2004 sink.short_description(L"Turn straight alpha output on or off for a channel.");
2005 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
2006 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
2007 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
2008 sink.para()->text(L"Examples:");
2009 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
2010 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
2012 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
2013 L"<< 201 MIXER OK\n"
2017 std::wstring mixer_straight_alpha_command(command_context& ctx)
2019 if (ctx.parameters.empty())
2021 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
2022 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
2025 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
2026 ctx.channel.channel->mixer().set_straight_alpha_output(state);
2028 return L"202 MIXER OK\r\n";
2031 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2033 sink.short_description(L"Create a grid of video layers.");
2034 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
2036 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
2037 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
2038 sink.para()->text(L"Examples:");
2039 sink.example(L">> MIXER 1 GRID 2");
2042 std::wstring mixer_grid_command(command_context& ctx)
2044 transforms_applier transforms(ctx);
2045 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
2046 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
2047 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
2048 double delta = 1.0 / static_cast<double>(n);
2049 for (int x = 0; x < n; ++x)
2051 for (int y = 0; y < n; ++y)
2053 int index = x + y*n + 1;
2054 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
2056 transform.image_transform.fill_translation[0] = x*delta;
2057 transform.image_transform.fill_translation[1] = y*delta;
2058 transform.image_transform.fill_scale[0] = delta;
2059 transform.image_transform.fill_scale[1] = delta;
2060 transform.image_transform.clip_translation[0] = x*delta;
2061 transform.image_transform.clip_translation[1] = y*delta;
2062 transform.image_transform.clip_scale[0] = delta;
2063 transform.image_transform.clip_scale[1] = delta;
2065 }, duration, tween));
2070 return L"202 MIXER OK\r\n";
2073 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
2075 sink.short_description(L"Commit all deferred mixer transforms.");
2076 sink.syntax(L"MIXER [video_channel:int] COMMIT");
2077 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
2078 sink.para()->text(L"Examples:");
2080 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
2081 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
2082 L">> MIXER 1 COMMIT");
2085 std::wstring mixer_commit_command(command_context& ctx)
2087 transforms_applier transforms(ctx);
2088 transforms.commit_deferred();
2090 return L"202 MIXER OK\r\n";
2093 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
2095 sink.short_description(L"Clear all transformations on a channel or layer.");
2096 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
2097 sink.para()->text(L"Clears all transformations on a channel or layer.");
2098 sink.para()->text(L"Examples:");
2099 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
2100 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
2103 std::wstring mixer_clear_command(command_context& ctx)
2105 int layer = ctx.layer_id;
2108 ctx.channel.channel->stage().clear_transforms();
2110 ctx.channel.channel->stage().clear_transforms(layer);
2112 return L"202 MIXER OK\r\n";
2115 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2117 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2118 sink.syntax(L"CHANNEL_GRID");
2119 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2121 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2122 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2125 std::wstring channel_grid_command(command_context& ctx)
2128 auto self = ctx.channels.back();
2130 core::diagnostics::scoped_call_context save;
2131 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2133 std::vector<std::wstring> params;
2134 params.push_back(L"SCREEN");
2135 params.push_back(L"0");
2136 params.push_back(L"NAME");
2137 params.push_back(L"Channel Grid Window");
2138 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage(), get_channels(ctx));
2140 self.channel->output().add(screen);
2142 for (auto& channel : ctx.channels)
2144 if (channel.channel != self.channel)
2146 core::diagnostics::call_context::for_thread().layer = index;
2147 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2148 self.channel->stage().load(index, producer, false);
2149 self.channel->stage().play(index);
2154 auto num_channels = ctx.channels.size() - 1;
2155 int square_side_length = std::ceil(std::sqrt(num_channels));
2157 ctx.channel_index = self.channel->index();
2159 ctx.parameters.clear();
2160 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2161 mixer_grid_command(ctx);
2163 return L"202 CHANNEL_GRID OK\r\n";
2166 // Thumbnail Commands
2168 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2170 sink.short_description(L"List thumbnails.");
2171 sink.syntax(L"THUMBNAIL LIST {[sub_directory:string]}");
2172 sink.para()->text(L"Lists thumbnails.");
2174 ->text(L"if the optional ")->code(L"sub_directory")
2175 ->text(L" is specified only the thumbnails in that sub directory will be returned.");
2176 sink.para()->text(L"Examples:");
2178 L">> THUMBNAIL LIST\n"
2179 L"<< 200 THUMBNAIL LIST OK\n"
2180 L"<< \"AMB\" 20130301T124409 1149\n"
2181 L"<< \"foo/bar\" 20130523T234001 244");
2184 std::wstring thumbnail_list_command(command_context& ctx)
2186 std::wstring sub_directory;
2188 if (!ctx.parameters.empty())
2189 sub_directory = ctx.parameters.at(0);
2191 std::wstringstream replyString;
2192 replyString << L"200 THUMBNAIL LIST OK\r\n";
2194 for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::thumbnail_folder(), sub_directory)), end; itr != end; ++itr)
2196 if (boost::filesystem::is_regular_file(itr->path()))
2198 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2201 auto relativePath = get_relative_without_extension(itr->path(), env::thumbnail_folder());
2202 auto str = relativePath.generic_wstring();
2204 if (str[0] == '\\' || str[0] == '/')
2205 str = std::wstring(str.begin() + 1, str.end());
2207 auto mtime = boost::filesystem::last_write_time(itr->path());
2208 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2209 auto file_size = boost::filesystem::file_size(itr->path());
2211 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2215 replyString << L"\r\n";
2217 return boost::to_upper_copy(replyString.str());
2220 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2222 sink.short_description(L"Retrieve a thumbnail.");
2223 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2224 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2225 sink.para()->text(L"Examples:");
2227 L">> THUMBNAIL RETRIEVE foo/bar\n"
2228 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2229 L"<< ...base64 data...");
2232 std::wstring thumbnail_retrieve_command(command_context& ctx)
2234 std::wstring filename = env::thumbnail_folder();
2235 filename.append(ctx.parameters.at(0));
2236 filename.append(L".png");
2238 std::wstring file_contents;
2240 auto found_file = find_case_insensitive(filename);
2243 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2245 if (file_contents.empty())
2246 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2248 std::wstringstream reply;
2250 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2251 reply << file_contents;
2256 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2258 sink.short_description(L"Regenerate a thumbnail.");
2259 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2260 sink.para()->text(L"Regenerates a thumbnail.");
2263 std::wstring thumbnail_generate_command(command_context& ctx)
2267 ctx.thumb_gen->generate(ctx.parameters.at(0));
2268 return L"202 THUMBNAIL GENERATE OK\r\n";
2271 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2274 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2276 sink.short_description(L"Regenerate all thumbnails.");
2277 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2278 sink.para()->text(L"Regenerates all thumbnails.");
2281 std::wstring thumbnail_generateall_command(command_context& ctx)
2285 ctx.thumb_gen->generate_all();
2286 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2289 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2294 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2296 sink.short_description(L"Get information about a media file.");
2297 sink.syntax(L"CINF [filename:string]");
2298 sink.para()->text(L"Returns information about a media file.");
2301 std::wstring cinf_command(command_context& ctx)
2304 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2306 auto path = itr->path();
2307 auto file = path.replace_extension(L"").filename().wstring();
2308 if (boost::iequals(file, ctx.parameters.at(0)))
2309 info += MediaInfo(itr->path(), ctx.media_info_repo);
2313 CASPAR_THROW_EXCEPTION(file_not_found());
2315 std::wstringstream replyString;
2316 replyString << L"200 CINF OK\r\n";
2317 replyString << info << "\r\n";
2319 return replyString.str();
2322 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2324 sink.short_description(L"List media files.");
2325 sink.syntax(L"CLS {[sub_directory:string]}");
2327 ->text(L"Lists media files in the ")->code(L"media")->text(L" folder. Use the command ")
2328 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2330 ->text(L"if the optional ")->code(L"sub_directory")
2331 ->text(L" is specified only the media files in that sub directory will be returned.");
2334 std::wstring cls_command(command_context& ctx)
2336 std::wstring sub_directory;
2338 if (!ctx.parameters.empty())
2339 sub_directory = ctx.parameters.at(0);
2341 std::wstringstream replyString;
2342 replyString << L"200 CLS OK\r\n";
2343 replyString << ListMedia(ctx.media_info_repo, sub_directory);
2344 replyString << L"\r\n";
2345 return boost::to_upper_copy(replyString.str());
2348 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2350 sink.short_description(L"List all fonts.");
2351 sink.syntax(L"FLS");
2353 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2354 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2355 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2358 std::wstring fls_command(command_context& ctx)
2360 std::wstringstream replyString;
2361 replyString << L"200 FLS OK\r\n";
2363 for (auto& font : core::text::list_fonts())
2364 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2366 replyString << L"\r\n";
2368 return replyString.str();
2371 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2373 sink.short_description(L"List templates.");
2374 sink.syntax(L"TLS {[sub_directory:string]}");
2376 ->text(L"Lists template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2377 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2379 ->text(L"if the optional ")->code(L"sub_directory")
2380 ->text(L" is specified only the template files in that sub directory will be returned.");
2383 std::wstring tls_command(command_context& ctx)
2385 std::wstring sub_directory;
2387 if (!ctx.parameters.empty())
2388 sub_directory = ctx.parameters.at(0);
2390 std::wstringstream replyString;
2391 replyString << L"200 TLS OK\r\n";
2393 replyString << ListTemplates(ctx.cg_registry, sub_directory);
2394 replyString << L"\r\n";
2396 return replyString.str();
2399 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2401 sink.short_description(L"Get version information.");
2402 sink.syntax(L"VERSION {[component:string]}");
2403 sink.para()->text(L"Returns the version of specified component.");
2404 sink.para()->text(L"Examples:");
2407 L"<< 201 VERSION OK\n"
2408 L"<< 2.1.0.f207a33 STABLE");
2410 L">> VERSION SERVER\n"
2411 L"<< 201 VERSION OK\n"
2412 L"<< 2.1.0.f207a33 STABLE");
2414 L">> VERSION FLASH\n"
2415 L"<< 201 VERSION OK\n"
2418 L">> VERSION TEMPLATEHOST\n"
2419 L"<< 201 VERSION OK\n"
2423 L"<< 201 VERSION OK\n"
2427 std::wstring version_command(command_context& ctx)
2429 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2431 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2433 return L"201 VERSION OK\r\n" + version + L"\r\n";
2436 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2439 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2441 sink.short_description(L"Get a list of the available channels.");
2442 sink.syntax(L"INFO");
2443 sink.para()->text(L"Retrieves a list of the available channels.");
2447 L"<< 1 720p5000 PLAYING\n"
2448 L"<< 2 PAL PLAYING");
2451 std::wstring info_command(command_context& ctx)
2453 std::wstringstream replyString;
2454 // This is needed for backwards compatibility with old clients
2455 replyString << L"200 INFO OK\r\n";
2456 for (size_t n = 0; n < ctx.channels.size(); ++n)
2457 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2458 replyString << L"\r\n";
2459 return replyString.str();
2462 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2464 std::wstringstream replyString;
2466 if (command.empty())
2467 replyString << L"201 INFO OK\r\n";
2469 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2471 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2472 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2473 replyString << L"\r\n";
2474 return replyString.str();
2477 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2479 sink.short_description(L"Get information about a channel or a layer.");
2480 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2481 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2482 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2485 std::wstring info_channel_command(command_context& ctx)
2487 boost::property_tree::wptree info;
2488 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2490 if (layer == std::numeric_limits<int>::min())
2492 info.add_child(L"channel", ctx.channel.channel->info())
2493 .add(L"index", ctx.channel_index);
2497 if (ctx.parameters.size() >= 1)
2499 if (boost::iequals(ctx.parameters.at(0), L"B"))
2500 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2502 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2506 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2510 return create_info_xml_reply(info);
2513 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2515 sink.short_description(L"Get information about a template.");
2516 sink.syntax(L"INFO TEMPLATE [template:string]");
2517 sink.para()->text(L"Gets information about the specified template.");
2520 std::wstring info_template_command(command_context& ctx)
2522 auto filename = ctx.parameters.at(0);
2524 std::wstringstream str;
2525 str << u16(ctx.cg_registry->read_meta_info(filename));
2526 boost::property_tree::wptree info;
2527 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2529 return create_info_xml_reply(info, L"TEMPLATE");
2532 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2534 sink.short_description(L"Get the contents of the configuration used.");
2535 sink.syntax(L"INFO CONFIG");
2536 sink.para()->text(L"Gets the contents of the configuration used.");
2539 std::wstring info_config_command(command_context& ctx)
2541 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2544 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2546 sink.short_description(L"Get information about the paths used.");
2547 sink.syntax(L"INFO PATHS");
2548 sink.para()->text(L"Gets information about the paths used.");
2551 std::wstring info_paths_command(command_context& ctx)
2553 boost::property_tree::wptree info;
2555 info.add(L"paths.media-path", caspar::env::media_folder());
2556 info.add(L"paths.log-path", caspar::env::log_folder());
2557 info.add(L"paths.data-path", caspar::env::data_folder());
2558 info.add(L"paths.template-path", caspar::env::template_folder());
2559 info.add(L"paths.thumbnail-path", caspar::env::thumbnail_folder());
2560 info.add(L"paths.font-path", caspar::env::font_folder());
2561 info.add(L"paths.initial-path", caspar::env::initial_folder() + L"/");
2563 return create_info_xml_reply(info, L"PATHS");
2566 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2568 sink.short_description(L"Get system information.");
2569 sink.syntax(L"INFO SYSTEM");
2570 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2573 std::wstring info_system_command(command_context& ctx)
2575 boost::property_tree::wptree info;
2577 info.add(L"system.name", caspar::system_product_name());
2578 info.add(L"system.os.description", caspar::os_description());
2579 info.add(L"system.cpu", caspar::cpu_info());
2581 ctx.system_info_repo->fill_information(info);
2583 return create_info_xml_reply(info, L"SYSTEM");
2586 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2588 sink.short_description(L"Get detailed information about all channels.");
2589 sink.syntax(L"INFO SERVER");
2590 sink.para()->text(L"Gets detailed information about all channels.");
2593 std::wstring info_server_command(command_context& ctx)
2595 boost::property_tree::wptree info;
2598 for (auto& channel : ctx.channels)
2599 info.add_child(L"channels.channel", channel.channel->info())
2600 .add(L"index", ++index);
2602 return create_info_xml_reply(info, L"SERVER");
2605 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2607 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2608 sink.syntax(L"INFO QUEUES");
2609 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2612 std::wstring info_queues_command(command_context& ctx)
2614 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2617 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2619 sink.short_description(L"Lists all known threads in the server.");
2620 sink.syntax(L"INFO THREADS");
2621 sink.para()->text(L"Lists all known threads in the server.");
2624 std::wstring info_threads_command(command_context& ctx)
2626 std::wstringstream replyString;
2627 replyString << L"200 INFO THREADS OK\r\n";
2629 for (auto& thread : get_thread_infos())
2631 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2634 replyString << L"\r\n";
2635 return replyString.str();
2638 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2640 sink.short_description(L"Get the current delay on a channel or a layer.");
2641 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2642 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2645 std::wstring info_delay_command(command_context& ctx)
2647 boost::property_tree::wptree info;
2648 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2650 if (layer == std::numeric_limits<int>::min())
2651 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2653 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2654 .add(L"index", layer);
2656 return create_info_xml_reply(info, L"DELAY");
2659 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2661 sink.short_description(L"Open the diagnostics window.");
2662 sink.syntax(L"DIAG");
2663 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2666 std::wstring diag_command(command_context& ctx)
2668 core::diagnostics::osd::show_graphs(true);
2670 return L"202 DIAG OK\r\n";
2673 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2675 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2676 sink.syntax(L"GL INFO");
2677 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2680 std::wstring gl_info_command(command_context& ctx)
2682 auto device = ctx.ogl_device;
2685 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2687 std::wstringstream result;
2688 result << L"201 GL INFO OK\r\n";
2690 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2691 auto info = device->info();
2692 boost::property_tree::write_xml(result, info, w);
2695 return result.str();
2698 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2700 sink.short_description(L"Release pooled OpenGL resources.");
2701 sink.syntax(L"GL GC");
2702 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2705 std::wstring gl_gc_command(command_context& ctx)
2707 auto device = ctx.ogl_device;
2710 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2712 device->gc().wait();
2714 return L"202 GL GC OK\r\n";
2717 static const int WIDTH = 80;
2719 struct max_width_sink : public core::help_sink
2721 std::size_t max_width = 0;
2723 void begin_item(const std::wstring& name) override
2725 max_width = std::max(name.length(), max_width);
2729 struct short_description_sink : public core::help_sink
2732 std::wstringstream& out;
2734 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2736 void begin_item(const std::wstring& name) override
2738 out << std::left << std::setw(width + 1) << name;
2741 void short_description(const std::wstring& short_description) override
2743 out << short_description << L"\r\n";
2747 struct simple_paragraph_builder : core::paragraph_builder
2749 std::wostringstream out;
2750 std::wstringstream& commit_to;
2752 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2753 ~simple_paragraph_builder()
2755 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2757 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2759 out << std::move(text);
2760 return shared_from_this();
2762 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2763 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2764 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2765 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2768 struct simple_definition_list_builder : core::definition_list_builder
2770 std::wstringstream& out;
2772 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2773 ~simple_definition_list_builder()
2778 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2780 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2781 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2782 return shared_from_this();
2786 struct long_description_sink : public core::help_sink
2788 std::wstringstream& out;
2790 long_description_sink(std::wstringstream& out) : out(out) { }
2792 void syntax(const std::wstring& syntax) override
2794 out << L"Syntax:\n";
2795 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2798 spl::shared_ptr<core::paragraph_builder> para() override
2800 return spl::make_shared<simple_paragraph_builder>(out);
2803 spl::shared_ptr<core::definition_list_builder> definitions() override
2805 return spl::make_shared<simple_definition_list_builder>(out);
2808 void example(const std::wstring& code, const std::wstring& caption) override
2810 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2812 if (!caption.empty())
2813 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2818 void begin_item(const std::wstring& name) override
2820 out << name << L"\n\n";
2824 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2826 std::wstringstream result;
2827 result << L"200 " << help_command << L" OK\r\n";
2828 max_width_sink width;
2829 ctx.help_repo->help(tags, width);
2830 short_description_sink sink(width.max_width, result);
2831 sink.width = width.max_width;
2832 ctx.help_repo->help(tags, sink);
2834 return result.str();
2837 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2839 std::wstringstream result;
2840 result << L"201 " << help_command << L" OK\r\n";
2841 auto joined = boost::join(ctx.parameters, L" ");
2842 long_description_sink sink(result);
2843 ctx.help_repo->help(tags, joined, sink);
2845 return result.str();
2848 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2850 sink.short_description(L"Show online help for AMCP commands.");
2851 sink.syntax(LR"(HELP {[command:string]})");
2852 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2853 sink.example(L">> HELP", L"Shows a list of commands.");
2854 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2857 std::wstring help_command(command_context& ctx)
2859 if (ctx.parameters.size() == 0)
2860 return create_help_list(L"HELP", ctx, { L"AMCP" });
2862 return create_help_details(L"HELP", ctx, { L"AMCP" });
2865 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2867 sink.short_description(L"Show online help for producers.");
2868 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2869 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2870 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2871 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2874 std::wstring help_producer_command(command_context& ctx)
2876 if (ctx.parameters.size() == 0)
2877 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2879 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2882 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2884 sink.short_description(L"Show online help for consumers.");
2885 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2886 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2887 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2888 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2891 std::wstring help_consumer_command(command_context& ctx)
2893 if (ctx.parameters.size() == 0)
2894 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2896 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2899 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2901 sink.short_description(L"Disconnect the session.");
2902 sink.syntax(L"BYE");
2904 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2905 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2908 std::wstring bye_command(command_context& ctx)
2910 ctx.client->disconnect();
2914 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2916 sink.short_description(L"Shutdown the server.");
2917 sink.syntax(L"KILL");
2918 sink.para()->text(L"Shuts the server down.");
2921 std::wstring kill_command(command_context& ctx)
2923 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2924 return L"202 KILL OK\r\n";
2927 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2929 sink.short_description(L"Shutdown the server with restart exit code.");
2930 sink.syntax(L"RESTART");
2932 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2933 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2936 std::wstring restart_command(command_context& ctx)
2938 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2939 return L"202 RESTART OK\r\n";
2942 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2944 sink.short_description(L"Lock or unlock access to a channel.");
2945 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2946 sink.para()->text(L"Allows for exclusive access to a channel.");
2947 sink.para()->text(L"Examples:");
2948 sink.example(L"LOCK 1 ACQUIRE secret");
2949 sink.example(L"LOCK 1 RELEASE");
2950 sink.example(L"LOCK 1 CLEAR");
2953 std::wstring lock_command(command_context& ctx)
2955 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2956 auto lock = ctx.channels.at(channel_index).lock;
2957 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2959 if (command == L"ACQUIRE")
2961 std::wstring lock_phrase = ctx.parameters.at(2);
2963 //TODO: read options
2965 //just lock one channel
2966 if (!lock->try_lock(lock_phrase, ctx.client))
2967 return L"503 LOCK ACQUIRE FAILED\r\n";
2969 return L"202 LOCK ACQUIRE OK\r\n";
2971 else if (command == L"RELEASE")
2973 lock->release_lock(ctx.client);
2974 return L"202 LOCK RELEASE OK\r\n";
2976 else if (command == L"CLEAR")
2978 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2979 std::wstring client_override_phrase;
2981 if (!override_phrase.empty())
2982 client_override_phrase = ctx.parameters.at(2);
2984 //just clear one channel
2985 if (client_override_phrase != override_phrase)
2986 return L"503 LOCK CLEAR FAILED\r\n";
2988 lock->clear_locks();
2990 return L"202 LOCK CLEAR OK\r\n";
2993 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2996 void req_describer(core::help_sink& sink, const core::help_repository& repo)
2998 sink.short_description(L"Perform any command with an additional request id identifying the response.");
2999 sink.syntax(L"REQ [request_id:string] COMMAND...");
3001 ->text(L"This special command modifies the AMCP protocol a little bit to prepend ")
3002 ->code(L"RES request_id")->text(L" to the response, in order to see what asynchronous response matches what request.");
3003 sink.para()->text(L"Examples:");
3004 sink.example(L"REQ unique PLAY 1-0 AMB\n");
3006 L">> REQ unique PLAY 1-0 AMB\n"
3007 L"<< RES unique 202 PLAY OK");
3011 void register_commands(amcp_command_repository& repo)
3013 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
3014 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
3015 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
3016 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
3017 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
3018 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
3019 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
3020 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
3021 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
3022 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
3023 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
3024 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
3025 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
3026 repo.register_command( L"Basic Commands", L"LOG CATEGORY", log_category_describer, log_category_command, 2);
3027 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
3028 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
3030 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
3031 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
3032 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
3033 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
3035 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
3036 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
3037 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
3038 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
3039 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
3040 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
3041 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
3042 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
3043 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
3045 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
3046 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
3047 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
3048 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
3049 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
3050 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
3051 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
3052 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
3053 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
3054 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
3055 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
3056 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
3057 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
3058 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
3059 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
3060 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
3061 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
3062 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
3063 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
3064 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
3065 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
3066 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
3068 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
3069 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
3070 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
3071 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
3073 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
3074 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
3075 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
3076 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
3077 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
3078 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
3079 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
3080 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
3081 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
3082 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
3083 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
3084 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
3085 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
3086 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
3087 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
3088 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
3089 repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
3090 repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
3091 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
3092 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
3093 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
3094 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
3095 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
3096 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
3098 repo.help_repo()->register_item({ L"AMCP", L"Protocol Commands" }, L"REQ", req_describer);
3102 }} //namespace caspar