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.");
2299 sink.para()->text(L"If a file with the same name exist in multiple directories, all of them are returned.");
2302 std::wstring cinf_command(command_context& ctx)
2305 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
2307 auto path = itr->path();
2308 auto file = path.stem().wstring();
2309 if (boost::iequals(file, ctx.parameters.at(0)))
2310 info += MediaInfo(itr->path(), ctx.media_info_repo);
2314 CASPAR_THROW_EXCEPTION(file_not_found());
2316 std::wstringstream replyString;
2317 replyString << L"200 CINF OK\r\n";
2318 replyString << info << "\r\n";
2320 return replyString.str();
2323 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2325 sink.short_description(L"List media files.");
2326 sink.syntax(L"CLS {[sub_directory:string]}");
2328 ->text(L"Lists media files in the ")->code(L"media")->text(L" folder. Use the command ")
2329 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2331 ->text(L"if the optional ")->code(L"sub_directory")
2332 ->text(L" is specified only the media files in that sub directory will be returned.");
2335 std::wstring cls_command(command_context& ctx)
2337 std::wstring sub_directory;
2339 if (!ctx.parameters.empty())
2340 sub_directory = ctx.parameters.at(0);
2342 std::wstringstream replyString;
2343 replyString << L"200 CLS OK\r\n";
2344 replyString << ListMedia(ctx.media_info_repo, sub_directory);
2345 replyString << L"\r\n";
2346 return boost::to_upper_copy(replyString.str());
2349 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2351 sink.short_description(L"List all fonts.");
2352 sink.syntax(L"FLS");
2354 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2355 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2356 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2359 std::wstring fls_command(command_context& ctx)
2361 std::wstringstream replyString;
2362 replyString << L"200 FLS OK\r\n";
2364 for (auto& font : core::text::list_fonts())
2365 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2367 replyString << L"\r\n";
2369 return replyString.str();
2372 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2374 sink.short_description(L"List templates.");
2375 sink.syntax(L"TLS {[sub_directory:string]}");
2377 ->text(L"Lists template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2378 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2380 ->text(L"if the optional ")->code(L"sub_directory")
2381 ->text(L" is specified only the template files in that sub directory will be returned.");
2384 std::wstring tls_command(command_context& ctx)
2386 std::wstring sub_directory;
2388 if (!ctx.parameters.empty())
2389 sub_directory = ctx.parameters.at(0);
2391 std::wstringstream replyString;
2392 replyString << L"200 TLS OK\r\n";
2394 replyString << ListTemplates(ctx.cg_registry, sub_directory);
2395 replyString << L"\r\n";
2397 return replyString.str();
2400 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2402 sink.short_description(L"Get version information.");
2403 sink.syntax(L"VERSION {[component:string]}");
2404 sink.para()->text(L"Returns the version of specified component.");
2405 sink.para()->text(L"Examples:");
2408 L"<< 201 VERSION OK\n"
2409 L"<< 2.1.0.f207a33 STABLE");
2411 L">> VERSION SERVER\n"
2412 L"<< 201 VERSION OK\n"
2413 L"<< 2.1.0.f207a33 STABLE");
2415 L">> VERSION FLASH\n"
2416 L"<< 201 VERSION OK\n"
2419 L">> VERSION TEMPLATEHOST\n"
2420 L"<< 201 VERSION OK\n"
2424 L"<< 201 VERSION OK\n"
2428 std::wstring version_command(command_context& ctx)
2430 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2432 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2434 return L"201 VERSION OK\r\n" + version + L"\r\n";
2437 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2440 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2442 sink.short_description(L"Get a list of the available channels.");
2443 sink.syntax(L"INFO");
2444 sink.para()->text(L"Retrieves a list of the available channels.");
2448 L"<< 1 720p5000 PLAYING\n"
2449 L"<< 2 PAL PLAYING");
2452 std::wstring info_command(command_context& ctx)
2454 std::wstringstream replyString;
2455 // This is needed for backwards compatibility with old clients
2456 replyString << L"200 INFO OK\r\n";
2457 for (size_t n = 0; n < ctx.channels.size(); ++n)
2458 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2459 replyString << L"\r\n";
2460 return replyString.str();
2463 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2465 std::wstringstream replyString;
2467 if (command.empty())
2468 replyString << L"201 INFO OK\r\n";
2470 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2472 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2473 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2474 replyString << L"\r\n";
2475 return replyString.str();
2478 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2480 sink.short_description(L"Get information about a channel or a layer.");
2481 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2482 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2483 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2486 std::wstring info_channel_command(command_context& ctx)
2488 boost::property_tree::wptree info;
2489 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2491 if (layer == std::numeric_limits<int>::min())
2493 info.add_child(L"channel", ctx.channel.channel->info())
2494 .add(L"index", ctx.channel_index);
2498 if (ctx.parameters.size() >= 1)
2500 if (boost::iequals(ctx.parameters.at(0), L"B"))
2501 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2503 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2507 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2511 return create_info_xml_reply(info);
2514 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2516 sink.short_description(L"Get information about a template.");
2517 sink.syntax(L"INFO TEMPLATE [template:string]");
2518 sink.para()->text(L"Gets information about the specified template.");
2521 std::wstring info_template_command(command_context& ctx)
2523 auto filename = ctx.parameters.at(0);
2525 std::wstringstream str;
2526 str << u16(ctx.cg_registry->read_meta_info(filename));
2527 boost::property_tree::wptree info;
2528 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2530 return create_info_xml_reply(info, L"TEMPLATE");
2533 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2535 sink.short_description(L"Get the contents of the configuration used.");
2536 sink.syntax(L"INFO CONFIG");
2537 sink.para()->text(L"Gets the contents of the configuration used.");
2540 std::wstring info_config_command(command_context& ctx)
2542 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2545 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2547 sink.short_description(L"Get information about the paths used.");
2548 sink.syntax(L"INFO PATHS");
2549 sink.para()->text(L"Gets information about the paths used.");
2552 std::wstring info_paths_command(command_context& ctx)
2554 boost::property_tree::wptree info;
2556 info.add(L"paths.media-path", caspar::env::media_folder());
2557 info.add(L"paths.log-path", caspar::env::log_folder());
2558 info.add(L"paths.data-path", caspar::env::data_folder());
2559 info.add(L"paths.template-path", caspar::env::template_folder());
2560 info.add(L"paths.thumbnail-path", caspar::env::thumbnail_folder());
2561 info.add(L"paths.font-path", caspar::env::font_folder());
2562 info.add(L"paths.initial-path", caspar::env::initial_folder() + L"/");
2564 return create_info_xml_reply(info, L"PATHS");
2567 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2569 sink.short_description(L"Get system information.");
2570 sink.syntax(L"INFO SYSTEM");
2571 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2574 std::wstring info_system_command(command_context& ctx)
2576 boost::property_tree::wptree info;
2578 info.add(L"system.name", caspar::system_product_name());
2579 info.add(L"system.os.description", caspar::os_description());
2580 info.add(L"system.cpu", caspar::cpu_info());
2582 ctx.system_info_repo->fill_information(info);
2584 return create_info_xml_reply(info, L"SYSTEM");
2587 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2589 sink.short_description(L"Get detailed information about all channels.");
2590 sink.syntax(L"INFO SERVER");
2591 sink.para()->text(L"Gets detailed information about all channels.");
2594 std::wstring info_server_command(command_context& ctx)
2596 boost::property_tree::wptree info;
2599 for (auto& channel : ctx.channels)
2600 info.add_child(L"channels.channel", channel.channel->info())
2601 .add(L"index", ++index);
2603 return create_info_xml_reply(info, L"SERVER");
2606 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2608 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2609 sink.syntax(L"INFO QUEUES");
2610 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2613 std::wstring info_queues_command(command_context& ctx)
2615 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2618 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2620 sink.short_description(L"Lists all known threads in the server.");
2621 sink.syntax(L"INFO THREADS");
2622 sink.para()->text(L"Lists all known threads in the server.");
2625 std::wstring info_threads_command(command_context& ctx)
2627 std::wstringstream replyString;
2628 replyString << L"200 INFO THREADS OK\r\n";
2630 for (auto& thread : get_thread_infos())
2632 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2635 replyString << L"\r\n";
2636 return replyString.str();
2639 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2641 sink.short_description(L"Get the current delay on a channel or a layer.");
2642 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2643 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2646 std::wstring info_delay_command(command_context& ctx)
2648 boost::property_tree::wptree info;
2649 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2651 if (layer == std::numeric_limits<int>::min())
2652 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2654 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2655 .add(L"index", layer);
2657 return create_info_xml_reply(info, L"DELAY");
2660 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2662 sink.short_description(L"Open the diagnostics window.");
2663 sink.syntax(L"DIAG");
2664 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2667 std::wstring diag_command(command_context& ctx)
2669 core::diagnostics::osd::show_graphs(true);
2671 return L"202 DIAG OK\r\n";
2674 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2676 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2677 sink.syntax(L"GL INFO");
2678 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2681 std::wstring gl_info_command(command_context& ctx)
2683 auto device = ctx.ogl_device;
2686 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2688 std::wstringstream result;
2689 result << L"201 GL INFO OK\r\n";
2691 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2692 auto info = device->info();
2693 boost::property_tree::write_xml(result, info, w);
2696 return result.str();
2699 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2701 sink.short_description(L"Release pooled OpenGL resources.");
2702 sink.syntax(L"GL GC");
2703 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2706 std::wstring gl_gc_command(command_context& ctx)
2708 auto device = ctx.ogl_device;
2711 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2713 device->gc().wait();
2715 return L"202 GL GC OK\r\n";
2718 static const int WIDTH = 80;
2720 struct max_width_sink : public core::help_sink
2722 std::size_t max_width = 0;
2724 void begin_item(const std::wstring& name) override
2726 max_width = std::max(name.length(), max_width);
2730 struct short_description_sink : public core::help_sink
2733 std::wstringstream& out;
2735 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2737 void begin_item(const std::wstring& name) override
2739 out << std::left << std::setw(width + 1) << name;
2742 void short_description(const std::wstring& short_description) override
2744 out << short_description << L"\r\n";
2748 struct simple_paragraph_builder : core::paragraph_builder
2750 std::wostringstream out;
2751 std::wstringstream& commit_to;
2753 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2754 ~simple_paragraph_builder()
2756 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2758 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2760 out << std::move(text);
2761 return shared_from_this();
2763 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2764 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2765 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2766 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2769 struct simple_definition_list_builder : core::definition_list_builder
2771 std::wstringstream& out;
2773 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2774 ~simple_definition_list_builder()
2779 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2781 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2782 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2783 return shared_from_this();
2787 struct long_description_sink : public core::help_sink
2789 std::wstringstream& out;
2791 long_description_sink(std::wstringstream& out) : out(out) { }
2793 void syntax(const std::wstring& syntax) override
2795 out << L"Syntax:\n";
2796 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2799 spl::shared_ptr<core::paragraph_builder> para() override
2801 return spl::make_shared<simple_paragraph_builder>(out);
2804 spl::shared_ptr<core::definition_list_builder> definitions() override
2806 return spl::make_shared<simple_definition_list_builder>(out);
2809 void example(const std::wstring& code, const std::wstring& caption) override
2811 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2813 if (!caption.empty())
2814 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2819 void begin_item(const std::wstring& name) override
2821 out << name << L"\n\n";
2825 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2827 std::wstringstream result;
2828 result << L"200 " << help_command << L" OK\r\n";
2829 max_width_sink width;
2830 ctx.help_repo->help(tags, width);
2831 short_description_sink sink(width.max_width, result);
2832 sink.width = width.max_width;
2833 ctx.help_repo->help(tags, sink);
2835 return result.str();
2838 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2840 std::wstringstream result;
2841 result << L"201 " << help_command << L" OK\r\n";
2842 auto joined = boost::join(ctx.parameters, L" ");
2843 long_description_sink sink(result);
2844 ctx.help_repo->help(tags, joined, sink);
2846 return result.str();
2849 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2851 sink.short_description(L"Show online help for AMCP commands.");
2852 sink.syntax(LR"(HELP {[command:string]})");
2853 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2854 sink.example(L">> HELP", L"Shows a list of commands.");
2855 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2858 std::wstring help_command(command_context& ctx)
2860 if (ctx.parameters.size() == 0)
2861 return create_help_list(L"HELP", ctx, { L"AMCP" });
2863 return create_help_details(L"HELP", ctx, { L"AMCP" });
2866 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2868 sink.short_description(L"Show online help for producers.");
2869 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2870 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2871 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2872 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2875 std::wstring help_producer_command(command_context& ctx)
2877 if (ctx.parameters.size() == 0)
2878 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2880 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2883 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2885 sink.short_description(L"Show online help for consumers.");
2886 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2887 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2888 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2889 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2892 std::wstring help_consumer_command(command_context& ctx)
2894 if (ctx.parameters.size() == 0)
2895 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2897 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2900 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2902 sink.short_description(L"Disconnect the session.");
2903 sink.syntax(L"BYE");
2905 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2906 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2909 std::wstring bye_command(command_context& ctx)
2911 ctx.client->disconnect();
2915 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2917 sink.short_description(L"Shutdown the server.");
2918 sink.syntax(L"KILL");
2919 sink.para()->text(L"Shuts the server down.");
2922 std::wstring kill_command(command_context& ctx)
2924 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2925 return L"202 KILL OK\r\n";
2928 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2930 sink.short_description(L"Shutdown the server with restart exit code.");
2931 sink.syntax(L"RESTART");
2933 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2934 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2937 std::wstring restart_command(command_context& ctx)
2939 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2940 return L"202 RESTART OK\r\n";
2943 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2945 sink.short_description(L"Lock or unlock access to a channel.");
2946 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2947 sink.para()->text(L"Allows for exclusive access to a channel.");
2948 sink.para()->text(L"Examples:");
2949 sink.example(L"LOCK 1 ACQUIRE secret");
2950 sink.example(L"LOCK 1 RELEASE");
2951 sink.example(L"LOCK 1 CLEAR");
2954 std::wstring lock_command(command_context& ctx)
2956 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2957 auto lock = ctx.channels.at(channel_index).lock;
2958 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2960 if (command == L"ACQUIRE")
2962 std::wstring lock_phrase = ctx.parameters.at(2);
2964 //TODO: read options
2966 //just lock one channel
2967 if (!lock->try_lock(lock_phrase, ctx.client))
2968 return L"503 LOCK ACQUIRE FAILED\r\n";
2970 return L"202 LOCK ACQUIRE OK\r\n";
2972 else if (command == L"RELEASE")
2974 lock->release_lock(ctx.client);
2975 return L"202 LOCK RELEASE OK\r\n";
2977 else if (command == L"CLEAR")
2979 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2980 std::wstring client_override_phrase;
2982 if (!override_phrase.empty())
2983 client_override_phrase = ctx.parameters.at(2);
2985 //just clear one channel
2986 if (client_override_phrase != override_phrase)
2987 return L"503 LOCK CLEAR FAILED\r\n";
2989 lock->clear_locks();
2991 return L"202 LOCK CLEAR OK\r\n";
2994 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2997 void req_describer(core::help_sink& sink, const core::help_repository& repo)
2999 sink.short_description(L"Perform any command with an additional request id identifying the response.");
3000 sink.syntax(L"REQ [request_id:string] COMMAND...");
3002 ->text(L"This special command modifies the AMCP protocol a little bit to prepend ")
3003 ->code(L"RES request_id")->text(L" to the response, in order to see what asynchronous response matches what request.");
3004 sink.para()->text(L"Examples:");
3005 sink.example(L"REQ unique PLAY 1-0 AMB\n");
3007 L">> REQ unique PLAY 1-0 AMB\n"
3008 L"<< RES unique 202 PLAY OK");
3012 void register_commands(amcp_command_repository& repo)
3014 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
3015 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
3016 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
3017 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
3018 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
3019 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
3020 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
3021 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
3022 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
3023 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
3024 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
3025 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
3026 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
3027 repo.register_command( L"Basic Commands", L"LOG CATEGORY", log_category_describer, log_category_command, 2);
3028 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
3029 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
3031 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
3032 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
3033 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
3034 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
3036 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
3037 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
3038 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
3039 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
3040 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
3041 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
3042 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
3043 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
3044 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
3046 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
3047 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
3048 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
3049 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
3050 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
3051 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
3052 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
3053 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
3054 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
3055 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
3056 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
3057 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
3058 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
3059 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
3060 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
3061 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
3062 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
3063 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
3064 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
3065 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
3066 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
3067 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
3069 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
3070 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
3071 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
3072 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
3074 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
3075 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
3076 repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0);
3077 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
3078 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
3079 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
3080 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
3081 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
3082 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
3083 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
3084 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
3085 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
3086 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
3087 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
3088 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
3089 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
3090 repo.register_command( L"Query Commands", L"GL INFO", gl_info_describer, gl_info_command, 0);
3091 repo.register_command( L"Query Commands", L"GL GC", gl_gc_describer, gl_gc_command, 0);
3092 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
3093 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
3094 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
3095 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
3096 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
3097 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
3099 repo.help_repo()->register_item({ L"AMCP", L"Protocol Commands" }, L"REQ", req_describer);
3103 }} //namespace caspar