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,
315 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
317 sink.short_description(L"Load a media file or resource in the background.");
318 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]})");
320 ->text(L"Loads a producer in the background and prepares it for playout. ")
321 ->text(L"If no layer is specified the default layer index will be used.");
323 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
324 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
326 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
327 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
329 ->code(L"loop")->text(L" will cause the clip to loop.");
331 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
333 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
335 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
336 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
337 sink.para()->text(L"Examples:");
338 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
339 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
340 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
341 sink.example(L">> LOADBG 1-0 MY_FILE");
343 L">> PLAY 1-1 MY_FILE\n"
344 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
345 L"To automatically fade out a layer after a video file has been played to the end");
347 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
349 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
350 ->code(L"filter")->text(L" command.");
353 std::wstring loadbg_command(command_context& ctx)
355 transition_info transitionInfo;
359 std::wstring message;
360 for (size_t n = 0; n < ctx.parameters.size(); ++n)
361 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
363 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)?.*)");
365 if (boost::regex_match(message, what, expr))
367 auto transition = what["TRANSITION"].str();
368 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
369 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
370 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
371 transitionInfo.tweener = tween;
373 if (transition == L"CUT")
374 transitionInfo.type = transition_type::cut;
375 else if (transition == L"MIX")
376 transitionInfo.type = transition_type::mix;
377 else if (transition == L"PUSH")
378 transitionInfo.type = transition_type::push;
379 else if (transition == L"SLIDE")
380 transitionInfo.type = transition_type::slide;
381 else if (transition == L"WIPE")
382 transitionInfo.type = transition_type::wipe;
384 if (direction == L"FROMLEFT")
385 transitionInfo.direction = transition_direction::from_left;
386 else if (direction == L"FROMRIGHT")
387 transitionInfo.direction = transition_direction::from_right;
388 else if (direction == L"LEFT")
389 transitionInfo.direction = transition_direction::from_right;
390 else if (direction == L"RIGHT")
391 transitionInfo.direction = transition_direction::from_left;
394 //Perform loading of the clip
395 core::diagnostics::scoped_call_context save;
396 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
397 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
399 auto channel = ctx.channel.channel;
400 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
402 if (pFP == frame_producer::empty())
403 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
405 bool auto_play = contains_param(L"AUTO", ctx.parameters);
407 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
409 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
411 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
413 return L"202 LOADBG OK\r\n";
416 void load_describer(core::help_sink& sink, const core::help_repository& repo)
418 sink.short_description(L"Load a media file or resource to the foreground.");
419 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
421 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
422 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
423 sink.para()->text(L"Examples:");
424 sink.example(L">> LOAD 1 MY_FILE");
425 sink.example(L">> LOAD 1-1 MY_FILE");
426 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
429 std::wstring load_command(command_context& ctx)
431 core::diagnostics::scoped_call_context save;
432 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
433 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
434 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
435 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
437 return L"202 LOAD OK\r\n";
440 void play_describer(core::help_sink& sink, const core::help_repository& repository)
442 sink.short_description(L"Play a media file or resource.");
443 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
445 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
446 ->text(L") is prepared, it will be executed.");
448 ->text(L"If additional parameters (see ")->see(L"LOADBG")
449 ->text(L") are provided then the provided clip will first be loaded to the background.");
450 sink.para()->text(L"Examples:");
451 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
452 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
453 sink.example(L">> PLAY 1-0 MY_FILE");
454 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
457 std::wstring play_command(command_context& ctx)
459 if (!ctx.parameters.empty())
462 ctx.channel.channel->stage().play(ctx.layer_index());
464 return L"202 PLAY OK\r\n";
467 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
469 sink.short_description(L"Pause playback of a layer.");
470 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
472 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
473 ->text(L" command can be used to resume playback again.");
474 sink.para()->text(L"Examples:");
475 sink.example(L">> PAUSE 1");
476 sink.example(L">> PAUSE 1-1");
479 std::wstring pause_command(command_context& ctx)
481 ctx.channel.channel->stage().pause(ctx.layer_index());
482 return L"202 PAUSE OK\r\n";
485 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
487 sink.short_description(L"Resume playback of a layer.");
488 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
490 ->text(L"Resumes playback of a foreground clip previously paused with the ")
491 ->see(L"PAUSE")->text(L" command.");
492 sink.para()->text(L"Examples:");
493 sink.example(L">> RESUME 1");
494 sink.example(L">> RESUME 1-1");
497 std::wstring resume_command(command_context& ctx)
499 ctx.channel.channel->stage().resume(ctx.layer_index());
500 return L"202 RESUME OK\r\n";
503 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
505 sink.short_description(L"Remove the foreground clip of a layer.");
506 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
508 ->text(L"Removes the foreground clip of the specified layer.");
509 sink.para()->text(L"Examples:");
510 sink.example(L">> STOP 1");
511 sink.example(L">> STOP 1-1");
514 std::wstring stop_command(command_context& ctx)
516 ctx.channel.channel->stage().stop(ctx.layer_index());
517 return L"202 STOP OK\r\n";
520 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
522 sink.short_description(L"Remove all clips of a layer or an entire channel.");
523 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
525 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
526 ->text(L"If no layer is specified then all layers in the specified ")
527 ->code(L"video_channel")->text(L" are cleared.");
528 sink.para()->text(L"Examples:");
529 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
530 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
533 std::wstring clear_command(command_context& ctx)
535 int index = ctx.layer_index(std::numeric_limits<int>::min());
536 if (index != std::numeric_limits<int>::min())
537 ctx.channel.channel->stage().clear(index);
539 ctx.channel.channel->stage().clear();
541 return L"202 CLEAR OK\r\n";
544 void call_describer(core::help_sink& sink, const core::help_repository& repo)
546 sink.short_description(L"Call a method on a producer.");
547 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
549 ->text(L"Calls method on the specified producer with the provided ")
550 ->code(L"param")->text(L" string.");
551 sink.para()->text(L"Examples:");
552 sink.example(L">> CALL 1 LOOP");
553 sink.example(L">> CALL 1-2 SEEK 25");
556 std::wstring call_command(command_context& ctx)
558 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters).get();
560 // TODO: because of std::async deferred timed waiting does not work
562 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
563 if (wait_res == std::future_status::timeout)
564 CASPAR_THROW_EXCEPTION(timed_out());*/
566 std::wstringstream replyString;
568 replyString << L"202 CALL OK\r\n";
570 replyString << L"201 CALL OK\r\n" << result << L"\r\n";
572 return replyString.str();
575 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
577 sink.short_description(L"Swap layers between channels.");
578 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
580 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
581 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
582 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
583 sink.para()->text(L"Examples:");
584 sink.example(L">> SWAP 1 2");
585 sink.example(L">> SWAP 1-1 2-3");
586 sink.example(L">> SWAP 1-1 1-2");
587 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
590 std::wstring swap_command(command_context& ctx)
592 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
594 if (ctx.layer_index(-1) != -1)
596 std::vector<std::string> strs;
597 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
599 auto ch1 = ctx.channel.channel;
600 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
602 int l1 = ctx.layer_index();
603 int l2 = boost::lexical_cast<int>(strs.at(1));
605 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
609 auto ch1 = ctx.channel.channel;
610 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
611 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
614 return L"202 SWAP OK\r\n";
617 void add_describer(core::help_sink& sink, const core::help_repository& repo)
619 sink.short_description(L"Add a consumer to a video channel.");
620 sink.syntax(L"ADD [video_channel:int]{-[consumer_index:int]} [consumer:string] [parameters:string]");
622 ->text(L"Adds a consumer to the specified video channel. The string ")
623 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
624 ->text(L"If a successful match is found a consumer will be created and added to the ")
625 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
626 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
627 ->see(L"the CasparCG config file")->text(L".");
629 ->text(L"Specifying ")->code(L"consumer_index")
630 ->text(L" overrides the index that the consumer itself decides and can later be used with the ")
631 ->see(L"REMOVE")->text(L" command to remove the consumer.");
632 sink.para()->text(L"Examples:");
633 sink.example(L">> ADD 1 DECKLINK 1");
634 sink.example(L">> ADD 1 BLUEFISH 2");
635 sink.example(L">> ADD 1 SCREEN");
636 sink.example(L">> ADD 1 AUDIO");
637 sink.example(L">> ADD 1 IMAGE filename");
638 sink.example(L">> ADD 2 SYNCTO 1");
639 sink.example(L">> ADD 1 FILE filename.mov");
640 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
642 L">> ADD 1-700 FILE filename.mov SEPARATE_KEY\n"
643 L">> REMOVE 1-700", L"overriding the consumer index to easier remove later.");
644 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
645 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
648 std::wstring add_command(command_context& ctx)
650 replace_placeholders(
651 L"<CLIENT_IP_ADDRESS>",
652 ctx.client->address(),
655 core::diagnostics::scoped_call_context save;
656 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
658 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx));
659 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
661 return L"202 ADD OK\r\n";
664 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
666 sink.short_description(L"Remove a consumer from a video channel.");
667 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
669 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
670 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
671 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
672 sink.para()->text(L"Examples:");
673 sink.example(L">> REMOVE 1 DECKLINK 1");
674 sink.example(L">> REMOVE 1 BLUEFISH 2");
675 sink.example(L">> REMOVE 1 SCREEN");
676 sink.example(L">> REMOVE 1 AUDIO");
677 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
680 std::wstring remove_command(command_context& ctx)
682 auto index = ctx.layer_index(std::numeric_limits<int>::min());
684 if (index == std::numeric_limits<int>::min())
686 replace_placeholders(
687 L"<CLIENT_IP_ADDRESS>",
688 ctx.client->address(),
691 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx))->index();
694 ctx.channel.channel->output().remove(index);
696 return L"202 REMOVE OK\r\n";
699 void print_describer(core::help_sink& sink, const core::help_repository& repo)
701 sink.short_description(L"Take a snapshot of a channel.");
702 sink.syntax(L"PRINT [video_channel:int]");
704 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
705 ->code(L"media")->text(L" folder.");
706 sink.para()->text(L"Examples:");
707 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
710 std::wstring print_command(command_context& ctx)
712 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage(), get_channels(ctx)));
714 return L"202 PRINT OK\r\n";
717 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
719 sink.short_description(L"Change the log level of the server.");
720 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
721 sink.para()->text(L"Changes the log level of the server.");
722 sink.para()->text(L"Examples:");
723 sink.example(L">> LOG LEVEL trace");
724 sink.example(L">> LOG LEVEL info");
727 std::wstring log_level_command(command_context& ctx)
729 log::set_log_level(ctx.parameters.at(0));
731 return L"202 LOG OK\r\n";
734 void log_category_describer(core::help_sink& sink, const core::help_repository& repo)
736 sink.short_description(L"Enable/disable a logging category in the server.");
737 sink.syntax(L"LOG CATEGORY [category:calltrace,communication] [enable:0,1]");
738 sink.para()->text(L"Enables or disables the specified logging category.");
739 sink.para()->text(L"Examples:");
740 sink.example(L">> LOG CATEGORY calltrace 1", L"to enable call trace");
741 sink.example(L">> LOG CATEGORY calltrace 0", L"to disable call trace");
744 std::wstring log_category_command(command_context& ctx)
746 log::set_log_category(ctx.parameters.at(0), ctx.parameters.at(1) == L"1");
748 return L"202 LOG OK\r\n";
751 void set_describer(core::help_sink& sink, const core::help_repository& repo)
753 sink.short_description(L"Change the value of a channel variable.");
754 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
755 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
757 ->item(L"MODE", L"Changes the video format of the channel.")
758 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
759 sink.para()->text(L"Examples:");
760 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
761 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
764 std::wstring set_command(command_context& ctx)
766 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
767 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
771 auto format_desc = core::video_format_desc(value);
772 if (format_desc.format != core::video_format::invalid)
774 ctx.channel.channel->video_format_desc(format_desc);
775 return L"202 SET MODE OK\r\n";
778 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video mode"));
780 else if (name == L"CHANNEL_LAYOUT")
782 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
786 ctx.channel.channel->audio_channel_layout(*channel_layout);
787 return L"202 SET CHANNEL_LAYOUT OK\r\n";
790 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid audio channel layout"));
793 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid channel variable"));
796 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
798 sink.short_description(L"Store a dataset.");
799 sink.syntax(L"DATA STORE [name:string] [data:string]");
800 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
801 sink.para()->text(L"Directories will be created if they do not exist.");
802 sink.para()->text(L"Examples:");
803 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
804 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
807 std::wstring data_store_command(command_context& ctx)
809 std::wstring filename = env::data_folder();
810 filename.append(ctx.parameters[0]);
811 filename.append(L".ftd");
813 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
814 auto found_data_path = find_case_insensitive(data_path);
817 data_path = *found_data_path;
819 if (!boost::filesystem::exists(data_path))
820 boost::filesystem::create_directories(data_path);
822 auto found_filename = find_case_insensitive(filename);
825 filename = *found_filename; // Overwrite case insensitive.
827 boost::filesystem::wofstream datafile(filename);
829 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
831 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
832 datafile << ctx.parameters[1] << std::flush;
835 return L"202 DATA STORE OK\r\n";
838 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
840 sink.short_description(L"Retrieve a dataset.");
841 sink.syntax(L"DATA RETRIEVE [name:string]");
842 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
843 sink.para()->text(L"Examples:");
844 sink.example(L">> DATA RETRIEVE my_data");
845 sink.example(L">> DATA RETRIEVE Folder1/my_data");
848 std::wstring data_retrieve_command(command_context& ctx)
850 std::wstring filename = env::data_folder();
851 filename.append(ctx.parameters[0]);
852 filename.append(L".ftd");
854 std::wstring file_contents;
856 auto found_file = find_case_insensitive(filename);
859 file_contents = read_file(boost::filesystem::path(*found_file));
861 if (file_contents.empty())
862 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
864 std::wstringstream reply;
865 reply << L"201 DATA RETRIEVE OK\r\n";
867 std::wstringstream file_contents_stream(file_contents);
870 bool firstLine = true;
871 while (std::getline(file_contents_stream, line))
885 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
887 sink.short_description(L"List stored datasets.");
888 sink.syntax(L"DATA LIST {[sub_directory:string]}");
889 sink.para()->text(L"Returns a list of stored datasets.");
891 ->text(L"if the optional ")->code(L"sub_directory")
892 ->text(L" is specified only the datasets in that sub directory will be returned.");
895 std::wstring data_list_command(command_context& ctx)
897 std::wstring sub_directory;
899 if (!ctx.parameters.empty())
900 sub_directory = ctx.parameters.at(0);
902 std::wstringstream replyString;
903 replyString << L"200 DATA LIST OK\r\n";
905 for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::data_folder(), sub_directory)), end; itr != end; ++itr)
907 if (boost::filesystem::is_regular_file(itr->path()))
909 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
912 auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
913 auto str = relativePath.generic_wstring();
915 if (str[0] == L'\\' || str[0] == L'/')
916 str = std::wstring(str.begin() + 1, str.end());
918 replyString << str << L"\r\n";
922 replyString << L"\r\n";
924 return boost::to_upper_copy(replyString.str());
927 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
929 sink.short_description(L"Remove a stored dataset.");
930 sink.syntax(L"DATA REMOVE [name:string]");
931 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
932 sink.para()->text(L"Examples:");
933 sink.example(L">> DATA REMOVE my_data");
934 sink.example(L">> DATA REMOVE Folder1/my_data");
937 std::wstring data_remove_command(command_context& ctx)
939 std::wstring filename = env::data_folder();
940 filename.append(ctx.parameters[0]);
941 filename.append(L".ftd");
943 if (!boost::filesystem::exists(filename))
944 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
946 if (!boost::filesystem::remove(filename))
947 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
949 return L"202 DATA REMOVE OK\r\n";
952 // Template Graphics Commands
954 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
956 sink.short_description(L"Prepare a template for displaying.");
957 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
959 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
960 ->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.");
961 sink.para()->text(L"Examples:");
962 sink.example(L">> CG 1 ADD 10 svtnews/info 1");
965 std::wstring cg_add_command(command_context& ctx)
967 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
969 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
970 std::wstring label; //_parameters[2]
971 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
972 unsigned int dataIndex = 3;
974 if (ctx.parameters.at(2).length() > 1)
976 label = ctx.parameters.at(2);
979 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
980 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
983 { //read play-on-load-flag
984 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
987 const wchar_t* pDataString = 0;
988 std::wstring dataFromFile;
989 if (ctx.parameters.size() > dataIndex)
991 const std::wstring& dataString = ctx.parameters.at(dataIndex);
993 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
994 pDataString = dataString.c_str();
997 //The data is not an XML-string, it must be a filename
998 std::wstring filename = env::data_folder();
999 filename.append(dataString);
1000 filename.append(L".ftd");
1002 auto found_file = find_case_insensitive(filename);
1006 dataFromFile = read_file(boost::filesystem::path(*found_file));
1007 pDataString = dataFromFile.c_str();
1012 auto filename = ctx.parameters.at(1);
1013 auto proxy = ctx.cg_registry->get_or_create_proxy(
1014 spl::make_shared_ptr(ctx.channel.channel),
1015 get_producer_dependencies(ctx.channel.channel, ctx),
1016 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
1019 if (proxy == core::cg_proxy::empty())
1020 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
1022 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
1024 return L"202 CG OK\r\n";
1027 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
1029 sink.short_description(L"Play and display a template.");
1030 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
1031 sink.para()->text(L"Plays and displays the template in the specified layer.");
1032 sink.para()->text(L"Examples:");
1033 sink.example(L">> CG 1 PLAY 0");
1036 std::wstring cg_play_command(command_context& ctx)
1038 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1039 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1041 return L"202 CG OK\r\n";
1044 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1046 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1048 if (proxy == cg_proxy::empty())
1049 CASPAR_THROW_EXCEPTION(expected_user_error() << msg_info(L"No CG proxy running on layer"));
1054 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1056 sink.short_description(L"Stop and remove a template.");
1057 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1059 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1060 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1061 sink.para()->text(L"Examples:");
1062 sink.example(L">> CG 1 STOP 0");
1065 std::wstring cg_stop_command(command_context& ctx)
1067 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1068 get_expected_cg_proxy(ctx)->stop(layer, 0);
1070 return L"202 CG OK\r\n";
1073 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1075 sink.short_description(LR"(Trigger a "continue" in a template.)");
1076 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1078 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1079 ->text(L"This is used to control animations that has multiple discreet steps.");
1080 sink.para()->text(L"Examples:");
1081 sink.example(L">> CG 1 NEXT 0");
1084 std::wstring cg_next_command(command_context& ctx)
1086 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1087 get_expected_cg_proxy(ctx)->next(layer);
1089 return L"202 CG OK\r\n";
1092 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1094 sink.short_description(L"Remove a template.");
1095 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1096 sink.para()->text(L"Removes the template from the specified layer.");
1097 sink.para()->text(L"Examples:");
1098 sink.example(L">> CG 1 REMOVE 0");
1101 std::wstring cg_remove_command(command_context& ctx)
1103 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1104 get_expected_cg_proxy(ctx)->remove(layer);
1106 return L"202 CG OK\r\n";
1109 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1111 sink.short_description(L"Remove all templates on a video layer.");
1112 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1113 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1114 sink.para()->text(L"Examples:");
1115 sink.example(L">> CG 1 CLEAR");
1118 std::wstring cg_clear_command(command_context& ctx)
1120 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1122 return L"202 CG OK\r\n";
1125 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1127 sink.short_description(L"Update a template with new data.");
1128 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1129 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.");
1132 std::wstring cg_update_command(command_context& ctx)
1134 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1136 std::wstring dataString = ctx.parameters.at(1);
1137 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1139 //The data is not XML or Json, it must be a filename
1140 std::wstring filename = env::data_folder();
1141 filename.append(dataString);
1142 filename.append(L".ftd");
1144 dataString = read_file(boost::filesystem::path(filename));
1147 get_expected_cg_proxy(ctx)->update(layer, dataString);
1149 return L"202 CG OK\r\n";
1152 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1154 sink.short_description(L"Invoke a method/label on a template.");
1155 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1156 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1157 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1160 std::wstring cg_invoke_command(command_context& ctx)
1162 std::wstringstream replyString;
1163 replyString << L"201 CG OK\r\n";
1164 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1165 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1166 replyString << result << L"\r\n";
1168 return replyString.str();
1171 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1173 sink.short_description(L"Get information about a running template or the template host.");
1174 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1175 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1176 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1179 std::wstring cg_info_command(command_context& ctx)
1181 std::wstringstream replyString;
1182 replyString << L"201 CG OK\r\n";
1184 if (ctx.parameters.empty())
1186 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1187 replyString << info << L"\r\n";
1191 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1192 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1194 replyString << desc << L"\r\n";
1197 return replyString.str();
1202 core::frame_transform get_current_transform(command_context& ctx)
1204 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1207 template<typename Func>
1208 std::wstring reply_value(command_context& ctx, const Func& extractor)
1210 auto value = extractor(get_current_transform(ctx));
1212 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1215 class transforms_applier
1217 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1219 std::vector<stage::transform_tuple_t> transforms_;
1220 command_context& ctx_;
1223 transforms_applier(command_context& ctx)
1226 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1229 ctx.parameters.pop_back();
1232 void add(stage::transform_tuple_t&& transform)
1234 transforms_.push_back(std::move(transform));
1237 void commit_deferred()
1239 auto& transforms = deferred_transforms_[ctx_.channel_index];
1240 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1248 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1249 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1252 ctx_.channel.channel->stage().apply_transforms(transforms_);
1255 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1257 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1259 sink.short_description(L"Let a layer act as alpha for the one obove.");
1260 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1262 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1263 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1264 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1265 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1266 ->text(L"instead it will be used as the key for the layer above.");
1267 sink.para()->text(L"Examples:");
1268 sink.example(L">> MIXER 1-0 KEYER 1");
1270 L">> MIXER 1-0 KEYER\n"
1271 L"<< 201 MIXER OK\n"
1272 L"<< 1", L"to retrieve the current state");
1275 std::wstring mixer_keyer_command(command_context& ctx)
1277 if (ctx.parameters.empty())
1278 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1280 transforms_applier transforms(ctx);
1281 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1282 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1284 transform.image_transform.is_key = value;
1286 }, 0, tweener(L"linear")));
1289 return L"202 MIXER OK\r\n";
1292 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1294 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1296 sink.short_description(L"Enable chroma keying on a layer.");
1297 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_suppress:float] [spill_suppress_saturation:float] [show_mask:0,1]}}" + ANIMATION_SYNTAX);
1299 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1300 sink.para()->text(L"The chroma keying is done in the HSB/HSV color space.");
1301 sink.para()->text(L"Parameters:");
1303 ->item(L"enable", L"0 to disable chroma keying on layer. The rest of the parameters should not be given when disabling.")
1304 ->item(L"target_hue", L"The hue in degrees between 0-360 where the center of the hue window will open up.")
1305 ->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.")
1306 ->item(L"min_saturation", L"The minimum saturation within 0.0-1.0 required for a color to be within the chroma window.")
1307 ->item(L"min_brightness", L"The minimum brightness within 0.0-1.0 required for a color to be within the chroma window.")
1308 ->item(L"softness", L"The softness of the chroma keying window.")
1309 ->item(L"spill_suppress", L"How much to suppress spill by within 0.0-180.0. It works by taking all hue values within +- this value from target_hue and clamps it to either target_hue - this value or target_hue + this value depending on which side it is closest to.")
1310 ->item(L"spill_suppress_saturation", L"Controls how much saturation should be kept on colors affected by spill_suppress within 0.0-1.0. Full saturation may not always be desirable to be kept on suppressed colors.")
1311 ->item(L"show_mask", L"If enabled, only shows the mask. Useful while editing the chroma key settings.")
1313 sink.example(L">> MIXER 1-1 CHROMA 1 120 0.1 0 0 0.1 0.1 0.7 0", L"for enabling chroma keying centered around a hue of 120 degrees (green) and with a 10% hue width");
1314 sink.example(L">> MIXER 1-1 CHROMA 0", L"for disabling chroma keying");
1316 L">> MIXER 1-1 CHROMA 0\n"
1317 L"<< 202 MIXER OK\n"
1318 L"<< 1 120 0.1 0 0 0.1 0 1 0", L"for getting the current chroma key mode");
1319 sink.para()->text(L"Deprecated legacy syntax:");
1320 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] [spill:float]}}" + ANIMATION_SYNTAX);
1321 sink.para()->text(L"Deprecated legacy examples:");
1322 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 1.0 25 easeinsine");
1323 sink.example(L">> MIXER 1-1 CHROMA none");
1326 std::wstring mixer_chroma_command(command_context& ctx)
1328 if (ctx.parameters.empty())
1330 auto chroma = get_current_transform(ctx).image_transform.chroma;
1331 return L"201 MIXER OK\r\n"
1332 + std::wstring(chroma.enable ? L"1 " : L"0 ")
1333 + boost::lexical_cast<std::wstring>(chroma.target_hue) + L" "
1334 + boost::lexical_cast<std::wstring>(chroma.hue_width) + L" "
1335 + boost::lexical_cast<std::wstring>(chroma.min_saturation) + L" "
1336 + boost::lexical_cast<std::wstring>(chroma.min_brightness) + L" "
1337 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1338 + boost::lexical_cast<std::wstring>(chroma.spill_suppress) + L" "
1339 + boost::lexical_cast<std::wstring>(chroma.spill_suppress_saturation) + L" "
1340 + std::wstring(chroma.show_mask ? L"1" : L"0") + L"\r\n";
1343 transforms_applier transforms(ctx);
1344 core::chroma chroma;
1349 auto legacy_mode = core::get_chroma_mode(ctx.parameters.at(0));
1354 duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1355 tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1357 if (*legacy_mode == chroma::legacy_type::none)
1359 chroma.enable = false;
1363 chroma.enable = true;
1364 chroma.hue_width = 0.5 - boost::lexical_cast<double>(ctx.parameters.at(1)) * 0.5;
1365 chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(1));
1366 chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(1));
1367 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2)) - boost::lexical_cast<double>(ctx.parameters.at(1));
1368 chroma.spill_suppress = 180.0 - boost::lexical_cast<double>(ctx.parameters.at(3)) * 180.0;
1369 chroma.spill_suppress_saturation = 1;
1371 if (*legacy_mode == chroma::legacy_type::green)
1372 chroma.target_hue = 120;
1373 else if (*legacy_mode == chroma::legacy_type::blue)
1374 chroma.target_hue = 240;
1379 duration = ctx.parameters.size() > 9 ? boost::lexical_cast<int>(ctx.parameters.at(9)) : 0;
1380 tween = ctx.parameters.size() > 10 ? ctx.parameters.at(10) : L"linear";
1382 chroma.enable = ctx.parameters.at(0) == L"1";
1386 chroma.target_hue = boost::lexical_cast<double>(ctx.parameters.at(1));
1387 chroma.hue_width = boost::lexical_cast<double>(ctx.parameters.at(2));
1388 chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(3));
1389 chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(4));
1390 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(5));
1391 chroma.spill_suppress = boost::lexical_cast<double>(ctx.parameters.at(6));
1392 chroma.spill_suppress_saturation = boost::lexical_cast<double>(ctx.parameters.at(7));
1393 chroma.show_mask = boost::lexical_cast<double>(ctx.parameters.at(8));
1398 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1400 transform.image_transform.chroma = chroma;
1402 }, duration, tween));
1405 return L"202 MIXER OK\r\n";
1408 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1410 sink.short_description(L"Set the blend mode for a layer.");
1411 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1413 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1414 ->text(L"If no argument is given the current blend mode is returned.");
1416 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1417 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1418 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1419 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1420 sink.para()->text(L"Examples:");
1421 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1423 L">> MIXER 1-1 BLEND\n"
1424 L"<< 201 MIXER OK\n"
1425 L"<< SCREEN", L"for getting the current blend mode");
1426 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1429 std::wstring mixer_blend_command(command_context& ctx)
1431 if (ctx.parameters.empty())
1432 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1434 transforms_applier transforms(ctx);
1435 auto value = get_blend_mode(ctx.parameters.at(0));
1436 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1438 transform.image_transform.blend_mode = value;
1440 }, 0, tweener(L"linear")));
1443 return L"202 MIXER OK\r\n";
1446 template<typename Getter, typename Setter>
1447 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1449 if (ctx.parameters.empty())
1450 return reply_value(ctx, getter);
1452 transforms_applier transforms(ctx);
1453 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1454 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1455 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1457 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1459 setter(transform, value);
1461 }, duration, tween));
1464 return L"202 MIXER OK\r\n";
1467 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1469 sink.short_description(L"Change the opacity of a layer.");
1470 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1471 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1472 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1473 sink.para()->text(L"Examples:");
1474 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1476 L">> MIXER 1-0 OPACITY\n"
1477 L"<< 201 MIXER OK\n"
1478 L"<< 0.5", L"to retrieve the current opacity");
1481 std::wstring mixer_opacity_command(command_context& ctx)
1483 return single_double_animatable_mixer_command(
1485 [](const frame_transform& t) { return t.image_transform.opacity; },
1486 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1489 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1491 sink.short_description(L"Change the brightness of a layer.");
1492 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1493 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1494 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1495 sink.para()->text(L"Examples:");
1496 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1498 L">> MIXER 1-0 BRIGHTNESS\n"
1499 L"<< 201 MIXER OK\n"
1500 L"0.5", L"to retrieve the current brightness");
1503 std::wstring mixer_brightness_command(command_context& ctx)
1505 return single_double_animatable_mixer_command(
1507 [](const frame_transform& t) { return t.image_transform.brightness; },
1508 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1511 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1513 sink.short_description(L"Change the saturation of a layer.");
1514 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1515 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1516 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1517 sink.para()->text(L"Examples:");
1518 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1520 L">> MIXER 1-0 SATURATION\n"
1521 L"<< 201 MIXER OK\n"
1522 L"<< 0.5", L"to retrieve the current saturation");
1525 std::wstring mixer_saturation_command(command_context& ctx)
1527 return single_double_animatable_mixer_command(
1529 [](const frame_transform& t) { return t.image_transform.saturation; },
1530 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1533 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1535 sink.short_description(L"Change the contrast of a layer.");
1536 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1537 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1538 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1539 sink.para()->text(L"Examples:");
1540 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1542 L">> MIXER 1-0 CONTRAST\n"
1543 L"<< 201 MIXER OK\n"
1544 L"<< 0.5", L"to retrieve the current contrast");
1547 std::wstring mixer_contrast_command(command_context& ctx)
1549 return single_double_animatable_mixer_command(
1551 [](const frame_transform& t) { return t.image_transform.contrast; },
1552 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1555 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1557 sink.short_description(L"Adjust the video levels of a layer.");
1558 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);
1560 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1562 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1563 ->item(L"gamma", L"Adjusts the gamma of the image.")
1564 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1565 sink.para()->text(L"Examples:");
1566 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");
1567 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");
1568 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1570 L">> MIXER 1-10 LEVELS\n"
1571 L"<< 201 MIXER OK\n"
1572 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1575 std::wstring mixer_levels_command(command_context& ctx)
1577 if (ctx.parameters.empty())
1579 auto levels = get_current_transform(ctx).image_transform.levels;
1580 return L"201 MIXER OK\r\n"
1581 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1582 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1583 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1584 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1585 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1588 transforms_applier transforms(ctx);
1590 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1591 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1592 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1593 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1594 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1595 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1596 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1598 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1600 transform.image_transform.levels = value;
1602 }, duration, tween));
1605 return L"202 MIXER OK\r\n";
1608 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1610 sink.short_description(L"Change the fill position and scale of a layer.");
1611 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1613 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1614 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1615 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1616 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1618 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1619 ->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.");
1620 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1622 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1623 ->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, ")
1624 ->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");
1626 ->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.")
1627 ->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.")
1628 ->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.")
1629 ->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.");
1630 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1631 sink.para()->text(L"Examples:");
1632 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1634 L">> MIXER 1-0 FILL\n"
1635 L"<< 201 MIXER OK\n"
1636 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1639 std::wstring mixer_fill_command(command_context& ctx)
1641 if (ctx.parameters.empty())
1643 auto transform = get_current_transform(ctx).image_transform;
1644 auto translation = transform.fill_translation;
1645 auto scale = transform.fill_scale;
1646 return L"201 MIXER OK\r\n"
1647 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1648 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1649 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1650 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1653 transforms_applier transforms(ctx);
1654 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1655 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1656 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1657 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1658 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1659 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1661 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1663 transform.image_transform.fill_translation[0] = x;
1664 transform.image_transform.fill_translation[1] = y;
1665 transform.image_transform.fill_scale[0] = x_s;
1666 transform.image_transform.fill_scale[1] = y_s;
1668 }, duration, tween));
1671 return L"202 MIXER OK\r\n";
1674 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1676 sink.short_description(L"Change the clipping viewport of a layer.");
1677 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1679 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1680 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1681 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1683 ->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.")
1684 ->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.")
1685 ->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.")
1686 ->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.");
1687 sink.para()->text(L"Examples:");
1688 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1690 L">> MIXER 1-0 CLIP\n"
1691 L"<< 201 MIXER OK\n"
1692 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1695 std::wstring mixer_clip_command(command_context& ctx)
1697 if (ctx.parameters.empty())
1699 auto transform = get_current_transform(ctx).image_transform;
1700 auto translation = transform.clip_translation;
1701 auto scale = transform.clip_scale;
1703 return L"201 MIXER OK\r\n"
1704 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1705 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1706 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1707 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1710 transforms_applier transforms(ctx);
1711 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1712 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1713 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1714 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1715 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1716 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1718 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1720 transform.image_transform.clip_translation[0] = x;
1721 transform.image_transform.clip_translation[1] = y;
1722 transform.image_transform.clip_scale[0] = x_s;
1723 transform.image_transform.clip_scale[1] = y_s;
1725 }, duration, tween));
1728 return L"202 MIXER OK\r\n";
1731 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1733 sink.short_description(L"Change the anchor point of a layer.");
1734 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1735 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1737 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1738 ->text(L" will be done from.");
1740 ->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.")
1741 ->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.");
1742 sink.para()->text(L"Examples:");
1743 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1745 L">> MIXER 1-10 ANCHOR\n"
1746 L"<< 201 MIXER OK\n"
1747 L"<< 0.5 0.6", L"gets the anchor point");
1750 std::wstring mixer_anchor_command(command_context& ctx)
1752 if (ctx.parameters.empty())
1754 auto transform = get_current_transform(ctx).image_transform;
1755 auto anchor = transform.anchor;
1756 return L"201 MIXER OK\r\n"
1757 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1758 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1761 transforms_applier transforms(ctx);
1762 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1763 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1764 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1765 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1767 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1769 transform.image_transform.anchor[0] = x;
1770 transform.image_transform.anchor[1] = y;
1772 }, duration, tween));
1775 return L"202 MIXER OK\r\n";
1778 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1780 sink.short_description(L"Crop a layer.");
1781 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);
1783 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1784 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1785 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1787 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1788 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1789 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1790 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1791 sink.para()->text(L"Examples:");
1792 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");
1794 L">> MIXER 1-0 CROP\n"
1795 L"<< 201 MIXER OK\n"
1796 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1799 std::wstring mixer_crop_command(command_context& ctx)
1801 if (ctx.parameters.empty())
1803 auto crop = get_current_transform(ctx).image_transform.crop;
1804 return L"201 MIXER OK\r\n"
1805 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1806 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1807 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1808 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1811 transforms_applier transforms(ctx);
1812 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1813 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1814 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1815 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1816 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1817 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1819 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1821 transform.image_transform.crop.ul[0] = ul_x;
1822 transform.image_transform.crop.ul[1] = ul_y;
1823 transform.image_transform.crop.lr[0] = lr_x;
1824 transform.image_transform.crop.lr[1] = lr_y;
1826 }, duration, tween));
1829 return L"202 MIXER OK\r\n";
1832 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1834 sink.short_description(L"Rotate a layer.");
1835 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1837 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1838 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1839 sink.para()->text(L"Examples:");
1840 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1842 L">> MIXER 1-0 ROTATION\n"
1843 L"<< 201 MIXER OK\n"
1844 L"<< 45", L"to retrieve the current angle");
1847 std::wstring mixer_rotation_command(command_context& ctx)
1849 static const double PI = 3.141592653589793;
1851 return single_double_animatable_mixer_command(
1853 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1854 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1857 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1859 sink.short_description(L"Adjust the perspective transform of a layer.");
1860 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);
1862 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1864 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1865 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1866 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1867 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1868 sink.para()->text(L"Examples:");
1869 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1871 L">> MIXER 1-10 PERSPECTIVE\n"
1872 L"<< 201 MIXER OK\n"
1873 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1876 std::wstring mixer_perspective_command(command_context& ctx)
1878 if (ctx.parameters.empty())
1880 auto perspective = get_current_transform(ctx).image_transform.perspective;
1883 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1884 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1885 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1886 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1887 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1888 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1889 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1890 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1893 transforms_applier transforms(ctx);
1894 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1895 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1896 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1897 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1898 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1899 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1900 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1901 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1902 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1903 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1905 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1907 transform.image_transform.perspective.ul[0] = ul_x;
1908 transform.image_transform.perspective.ul[1] = ul_y;
1909 transform.image_transform.perspective.ur[0] = ur_x;
1910 transform.image_transform.perspective.ur[1] = ur_y;
1911 transform.image_transform.perspective.lr[0] = lr_x;
1912 transform.image_transform.perspective.lr[1] = lr_y;
1913 transform.image_transform.perspective.ll[0] = ll_x;
1914 transform.image_transform.perspective.ll[1] = ll_y;
1916 }, duration, tween));
1919 return L"202 MIXER OK\r\n";
1922 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1924 sink.short_description(L"Enable or disable mipmapping for a layer.");
1925 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1927 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1928 ->text(L"If no argument is given the current state is returned.");
1929 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1930 sink.para()->text(L"Examples:");
1931 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1933 L">> MIXER 1-10 MIPMAP\n"
1934 L"<< 201 MIXER OK\n"
1935 L"<< 1", L"for getting the current state");
1938 std::wstring mixer_mipmap_command(command_context& ctx)
1940 if (ctx.parameters.empty())
1941 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1943 transforms_applier transforms(ctx);
1944 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1945 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1947 transform.image_transform.use_mipmap = value;
1949 }, 0, tweener(L"linear")));
1952 return L"202 MIXER OK\r\n";
1955 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1957 sink.short_description(L"Change the volume of a layer.");
1958 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1959 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1960 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1961 sink.para()->text(L"Examples:");
1962 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1963 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1965 L">> MIXER 1-0 VOLUME\n"
1966 L"<< 201 MIXER OK\n"
1967 L"<< 0.8", L"to retrieve the current volume");
1970 std::wstring mixer_volume_command(command_context& ctx)
1972 return single_double_animatable_mixer_command(
1974 [](const frame_transform& t) { return t.audio_transform.volume; },
1975 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1978 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1980 sink.short_description(L"Change the volume of an entire channel.");
1981 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1982 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1983 sink.para()->text(L"Examples:");
1984 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1985 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1986 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1989 std::wstring mixer_mastervolume_command(command_context& ctx)
1991 if (ctx.parameters.empty())
1993 auto volume = ctx.channel.channel->mixer().get_master_volume();
1994 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1997 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1998 ctx.channel.channel->mixer().set_master_volume(master_volume);
2000 return L"202 MIXER OK\r\n";
2003 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
2005 sink.short_description(L"Turn straight alpha output on or off for a channel.");
2006 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
2007 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
2008 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
2009 sink.para()->text(L"Examples:");
2010 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
2011 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
2013 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
2014 L"<< 201 MIXER OK\n"
2018 std::wstring mixer_straight_alpha_command(command_context& ctx)
2020 if (ctx.parameters.empty())
2022 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
2023 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
2026 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
2027 ctx.channel.channel->mixer().set_straight_alpha_output(state);
2029 return L"202 MIXER OK\r\n";
2032 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2034 sink.short_description(L"Create a grid of video layers.");
2035 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
2037 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
2038 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
2039 sink.para()->text(L"Examples:");
2040 sink.example(L">> MIXER 1 GRID 2");
2043 std::wstring mixer_grid_command(command_context& ctx)
2045 transforms_applier transforms(ctx);
2046 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
2047 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
2048 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
2049 double delta = 1.0 / static_cast<double>(n);
2050 for (int x = 0; x < n; ++x)
2052 for (int y = 0; y < n; ++y)
2054 int index = x + y*n + 1;
2055 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
2057 transform.image_transform.fill_translation[0] = x*delta;
2058 transform.image_transform.fill_translation[1] = y*delta;
2059 transform.image_transform.fill_scale[0] = delta;
2060 transform.image_transform.fill_scale[1] = delta;
2061 transform.image_transform.clip_translation[0] = x*delta;
2062 transform.image_transform.clip_translation[1] = y*delta;
2063 transform.image_transform.clip_scale[0] = delta;
2064 transform.image_transform.clip_scale[1] = delta;
2066 }, duration, tween));
2071 return L"202 MIXER OK\r\n";
2074 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
2076 sink.short_description(L"Commit all deferred mixer transforms.");
2077 sink.syntax(L"MIXER [video_channel:int] COMMIT");
2078 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
2079 sink.para()->text(L"Examples:");
2081 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
2082 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
2083 L">> MIXER 1 COMMIT");
2086 std::wstring mixer_commit_command(command_context& ctx)
2088 transforms_applier transforms(ctx);
2089 transforms.commit_deferred();
2091 return L"202 MIXER OK\r\n";
2094 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
2096 sink.short_description(L"Clear all transformations on a channel or layer.");
2097 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
2098 sink.para()->text(L"Clears all transformations on a channel or layer.");
2099 sink.para()->text(L"Examples:");
2100 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
2101 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
2104 std::wstring mixer_clear_command(command_context& ctx)
2106 int layer = ctx.layer_id;
2109 ctx.channel.channel->stage().clear_transforms();
2111 ctx.channel.channel->stage().clear_transforms(layer);
2113 return L"202 MIXER OK\r\n";
2116 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2118 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2119 sink.syntax(L"CHANNEL_GRID");
2120 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2122 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2123 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2126 std::wstring channel_grid_command(command_context& ctx)
2129 auto self = ctx.channels.back();
2131 core::diagnostics::scoped_call_context save;
2132 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2134 std::vector<std::wstring> params;
2135 params.push_back(L"SCREEN");
2136 params.push_back(L"0");
2137 params.push_back(L"NAME");
2138 params.push_back(L"Channel Grid Window");
2139 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage(), get_channels(ctx));
2141 self.channel->output().add(screen);
2143 for (auto& channel : ctx.channels)
2145 if (channel.channel != self.channel)
2147 core::diagnostics::call_context::for_thread().layer = index;
2148 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2149 self.channel->stage().load(index, producer, false);
2150 self.channel->stage().play(index);
2155 auto num_channels = ctx.channels.size() - 1;
2156 int square_side_length = std::ceil(std::sqrt(num_channels));
2158 ctx.channel_index = self.channel->index();
2160 ctx.parameters.clear();
2161 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2162 mixer_grid_command(ctx);
2164 return L"202 CHANNEL_GRID OK\r\n";
2167 // Thumbnail Commands
2169 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2171 sink.short_description(L"List thumbnails.");
2172 sink.syntax(L"THUMBNAIL LIST {[sub_directory:string]}");
2173 sink.para()->text(L"Lists thumbnails.");
2175 ->text(L"if the optional ")->code(L"sub_directory")
2176 ->text(L" is specified only the thumbnails in that sub directory will be returned.");
2177 sink.para()->text(L"Examples:");
2179 L">> THUMBNAIL LIST\n"
2180 L"<< 200 THUMBNAIL LIST OK\n"
2181 L"<< \"AMB\" 20130301T124409 1149\n"
2182 L"<< \"foo/bar\" 20130523T234001 244");
2185 std::wstring thumbnail_list_command(command_context& ctx)
2187 std::wstring sub_directory;
2189 if (!ctx.parameters.empty())
2190 sub_directory = ctx.parameters.at(0);
2192 std::wstringstream replyString;
2193 replyString << L"200 THUMBNAIL LIST OK\r\n";
2195 for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::thumbnail_folder(), sub_directory)), end; itr != end; ++itr)
2197 if (boost::filesystem::is_regular_file(itr->path()))
2199 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2202 auto relativePath = get_relative_without_extension(itr->path(), env::thumbnail_folder());
2203 auto str = relativePath.generic_wstring();
2205 if (str[0] == '\\' || str[0] == '/')
2206 str = std::wstring(str.begin() + 1, str.end());
2208 auto mtime = boost::filesystem::last_write_time(itr->path());
2209 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2210 auto file_size = boost::filesystem::file_size(itr->path());
2212 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2216 replyString << L"\r\n";
2218 return boost::to_upper_copy(replyString.str());
2221 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2223 sink.short_description(L"Retrieve a thumbnail.");
2224 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2225 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2226 sink.para()->text(L"Examples:");
2228 L">> THUMBNAIL RETRIEVE foo/bar\n"
2229 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2230 L"<< ...base64 data...");
2233 std::wstring thumbnail_retrieve_command(command_context& ctx)
2235 std::wstring filename = env::thumbnail_folder();
2236 filename.append(ctx.parameters.at(0));
2237 filename.append(L".png");
2239 std::wstring file_contents;
2241 auto found_file = find_case_insensitive(filename);
2244 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2246 if (file_contents.empty())
2247 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2249 std::wstringstream reply;
2251 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2252 reply << file_contents;
2257 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2259 sink.short_description(L"Regenerate a thumbnail.");
2260 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2261 sink.para()->text(L"Regenerates a thumbnail.");
2264 std::wstring thumbnail_generate_command(command_context& ctx)
2268 ctx.thumb_gen->generate(ctx.parameters.at(0));
2269 return L"202 THUMBNAIL GENERATE OK\r\n";
2272 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2275 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2277 sink.short_description(L"Regenerate all thumbnails.");
2278 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2279 sink.para()->text(L"Regenerates all thumbnails.");
2282 std::wstring thumbnail_generateall_command(command_context& ctx)
2286 ctx.thumb_gen->generate_all();
2287 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2290 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2295 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2297 sink.short_description(L"Get information about a media file.");
2298 sink.syntax(L"CINF [filename:string]");
2299 sink.para()->text(L"Returns information about a media file.");
2300 sink.para()->text(L"If a file with the same name exist in multiple directories, all of them are returned.");
2303 std::wstring cinf_command(command_context& ctx)
2306 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
2308 auto path = itr->path();
2309 auto file = path.stem().wstring();
2310 if (boost::iequals(file, ctx.parameters.at(0)))
2311 info += MediaInfo(itr->path(), ctx.media_info_repo);
2315 CASPAR_THROW_EXCEPTION(file_not_found());
2317 std::wstringstream replyString;
2318 replyString << L"200 CINF OK\r\n";
2319 replyString << info << "\r\n";
2321 return replyString.str();
2324 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2326 sink.short_description(L"List media files.");
2327 sink.syntax(L"CLS {[sub_directory:string]}");
2329 ->text(L"Lists media files in the ")->code(L"media")->text(L" folder. Use the command ")
2330 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2332 ->text(L"if the optional ")->code(L"sub_directory")
2333 ->text(L" is specified only the media files in that sub directory will be returned.");
2336 std::wstring cls_command(command_context& ctx)
2338 std::wstring sub_directory;
2340 if (!ctx.parameters.empty())
2341 sub_directory = ctx.parameters.at(0);
2343 std::wstringstream replyString;
2344 replyString << L"200 CLS OK\r\n";
2345 replyString << ListMedia(ctx.media_info_repo, sub_directory);
2346 replyString << L"\r\n";
2347 return boost::to_upper_copy(replyString.str());
2350 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2352 sink.short_description(L"List all fonts.");
2353 sink.syntax(L"FLS");
2355 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2356 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2357 sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2360 std::wstring fls_command(command_context& ctx)
2362 std::wstringstream replyString;
2363 replyString << L"200 FLS OK\r\n";
2365 for (auto& font : core::text::list_fonts())
2366 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2368 replyString << L"\r\n";
2370 return replyString.str();
2373 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2375 sink.short_description(L"List templates.");
2376 sink.syntax(L"TLS {[sub_directory:string]}");
2378 ->text(L"Lists template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2379 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2381 ->text(L"if the optional ")->code(L"sub_directory")
2382 ->text(L" is specified only the template files in that sub directory will be returned.");
2385 std::wstring tls_command(command_context& ctx)
2387 std::wstring sub_directory;
2389 if (!ctx.parameters.empty())
2390 sub_directory = ctx.parameters.at(0);
2392 std::wstringstream replyString;
2393 replyString << L"200 TLS OK\r\n";
2395 replyString << ListTemplates(ctx.cg_registry, sub_directory);
2396 replyString << L"\r\n";
2398 return replyString.str();
2401 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2403 sink.short_description(L"Get version information.");
2404 sink.syntax(L"VERSION {[component:string]}");
2405 sink.para()->text(L"Returns the version of specified component.");
2406 sink.para()->text(L"Examples:");
2409 L"<< 201 VERSION OK\n"
2410 L"<< 2.1.0.f207a33 STABLE");
2412 L">> VERSION SERVER\n"
2413 L"<< 201 VERSION OK\n"
2414 L"<< 2.1.0.f207a33 STABLE");
2416 L">> VERSION FLASH\n"
2417 L"<< 201 VERSION OK\n"
2420 L">> VERSION TEMPLATEHOST\n"
2421 L"<< 201 VERSION OK\n"
2425 L"<< 201 VERSION OK\n"
2429 std::wstring version_command(command_context& ctx)
2431 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2433 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2435 return L"201 VERSION OK\r\n" + version + L"\r\n";
2438 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2441 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2443 sink.short_description(L"Get a list of the available channels.");
2444 sink.syntax(L"INFO");
2445 sink.para()->text(L"Retrieves a list of the available channels.");
2449 L"<< 1 720p5000 PLAYING\n"
2450 L"<< 2 PAL PLAYING");
2453 std::wstring info_command(command_context& ctx)
2455 std::wstringstream replyString;
2456 // This is needed for backwards compatibility with old clients
2457 replyString << L"200 INFO OK\r\n";
2458 for (size_t n = 0; n < ctx.channels.size(); ++n)
2459 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2460 replyString << L"\r\n";
2461 return replyString.str();
2464 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2466 std::wstringstream replyString;
2468 if (command.empty())
2469 replyString << L"201 INFO OK\r\n";
2471 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2473 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2474 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2475 replyString << L"\r\n";
2476 return replyString.str();
2479 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2481 sink.short_description(L"Get information about a channel or a layer.");
2482 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2483 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2484 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2487 std::wstring info_channel_command(command_context& ctx)
2489 boost::property_tree::wptree info;
2490 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2492 if (layer == std::numeric_limits<int>::min())
2494 info.add_child(L"channel", ctx.channel.channel->info())
2495 .add(L"index", ctx.channel_index);
2499 if (ctx.parameters.size() >= 1)
2501 if (boost::iequals(ctx.parameters.at(0), L"B"))
2502 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2504 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2508 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2512 return create_info_xml_reply(info);
2515 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2517 sink.short_description(L"Get information about a template.");
2518 sink.syntax(L"INFO TEMPLATE [template:string]");
2519 sink.para()->text(L"Gets information about the specified template.");
2522 std::wstring info_template_command(command_context& ctx)
2524 auto filename = ctx.parameters.at(0);
2526 std::wstringstream str;
2527 str << u16(ctx.cg_registry->read_meta_info(filename));
2528 boost::property_tree::wptree info;
2529 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2531 return create_info_xml_reply(info, L"TEMPLATE");
2534 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2536 sink.short_description(L"Get the contents of the configuration used.");
2537 sink.syntax(L"INFO CONFIG");
2538 sink.para()->text(L"Gets the contents of the configuration used.");
2541 std::wstring info_config_command(command_context& ctx)
2543 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2546 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2548 sink.short_description(L"Get information about the paths used.");
2549 sink.syntax(L"INFO PATHS");
2550 sink.para()->text(L"Gets information about the paths used.");
2553 std::wstring info_paths_command(command_context& ctx)
2555 boost::property_tree::wptree info;
2557 info.add(L"paths.media-path", caspar::env::media_folder());
2558 info.add(L"paths.log-path", caspar::env::log_folder());
2559 info.add(L"paths.data-path", caspar::env::data_folder());
2560 info.add(L"paths.template-path", caspar::env::template_folder());
2561 info.add(L"paths.thumbnail-path", caspar::env::thumbnail_folder());
2562 info.add(L"paths.font-path", caspar::env::font_folder());
2563 info.add(L"paths.initial-path", caspar::env::initial_folder() + L"/");
2565 return create_info_xml_reply(info, L"PATHS");
2568 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2570 sink.short_description(L"Get system information.");
2571 sink.syntax(L"INFO SYSTEM");
2572 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2575 std::wstring info_system_command(command_context& ctx)
2577 boost::property_tree::wptree info;
2579 info.add(L"system.name", caspar::system_product_name());
2580 info.add(L"system.os.description", caspar::os_description());
2581 info.add(L"system.cpu", caspar::cpu_info());
2583 ctx.system_info_repo->fill_information(info);
2585 return create_info_xml_reply(info, L"SYSTEM");
2588 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2590 sink.short_description(L"Get detailed information about all channels.");
2591 sink.syntax(L"INFO SERVER");
2592 sink.para()->text(L"Gets detailed information about all channels.");
2595 std::wstring info_server_command(command_context& ctx)
2597 boost::property_tree::wptree info;
2600 for (auto& channel : ctx.channels)
2601 info.add_child(L"channels.channel", channel.channel->info())
2602 .add(L"index", ++index);
2604 return create_info_xml_reply(info, L"SERVER");
2607 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2609 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2610 sink.syntax(L"INFO QUEUES");
2611 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2614 std::wstring info_queues_command(command_context& ctx)
2616 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2619 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2621 sink.short_description(L"Lists all known threads in the server.");
2622 sink.syntax(L"INFO THREADS");
2623 sink.para()->text(L"Lists all known threads in the server.");
2626 std::wstring info_threads_command(command_context& ctx)
2628 std::wstringstream replyString;
2629 replyString << L"200 INFO THREADS OK\r\n";
2631 for (auto& thread : get_thread_infos())
2633 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2636 replyString << L"\r\n";
2637 return replyString.str();
2640 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2642 sink.short_description(L"Get the current delay on a channel or a layer.");
2643 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2644 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2647 std::wstring info_delay_command(command_context& ctx)
2649 boost::property_tree::wptree info;
2650 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2652 if (layer == std::numeric_limits<int>::min())
2653 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2655 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2656 .add(L"index", layer);
2658 return create_info_xml_reply(info, L"DELAY");
2661 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2663 sink.short_description(L"Open the diagnostics window.");
2664 sink.syntax(L"DIAG");
2665 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2668 std::wstring diag_command(command_context& ctx)
2670 core::diagnostics::osd::show_graphs(true);
2672 return L"202 DIAG OK\r\n";
2675 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2677 sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2678 sink.syntax(L"GL INFO");
2679 sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2682 std::wstring gl_info_command(command_context& ctx)
2684 auto device = ctx.ogl_device;
2687 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2689 std::wstringstream result;
2690 result << L"201 GL INFO OK\r\n";
2692 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2693 auto info = device->info();
2694 boost::property_tree::write_xml(result, info, w);
2697 return result.str();
2700 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2702 sink.short_description(L"Release pooled OpenGL resources.");
2703 sink.syntax(L"GL GC");
2704 sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2707 std::wstring gl_gc_command(command_context& ctx)
2709 auto device = ctx.ogl_device;
2712 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2714 device->gc().wait();
2716 return L"202 GL GC OK\r\n";
2719 static const int WIDTH = 80;
2721 struct max_width_sink : public core::help_sink
2723 std::size_t max_width = 0;
2725 void begin_item(const std::wstring& name) override
2727 max_width = std::max(name.length(), max_width);
2731 struct short_description_sink : public core::help_sink
2734 std::wstringstream& out;
2736 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2738 void begin_item(const std::wstring& name) override
2740 out << std::left << std::setw(width + 1) << name;
2743 void short_description(const std::wstring& short_description) override
2745 out << short_description << L"\r\n";
2749 struct simple_paragraph_builder : core::paragraph_builder
2751 std::wostringstream out;
2752 std::wstringstream& commit_to;
2754 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2755 ~simple_paragraph_builder()
2757 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2759 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2761 out << std::move(text);
2762 return shared_from_this();
2764 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2765 spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2766 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2767 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2770 struct simple_definition_list_builder : core::definition_list_builder
2772 std::wstringstream& out;
2774 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2775 ~simple_definition_list_builder()
2780 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2782 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2783 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2784 return shared_from_this();
2788 struct long_description_sink : public core::help_sink
2790 std::wstringstream& out;
2792 long_description_sink(std::wstringstream& out) : out(out) { }
2794 void syntax(const std::wstring& syntax) override
2796 out << L"Syntax:\n";
2797 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2800 spl::shared_ptr<core::paragraph_builder> para() override
2802 return spl::make_shared<simple_paragraph_builder>(out);
2805 spl::shared_ptr<core::definition_list_builder> definitions() override
2807 return spl::make_shared<simple_definition_list_builder>(out);
2810 void example(const std::wstring& code, const std::wstring& caption) override
2812 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2814 if (!caption.empty())
2815 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2820 void begin_item(const std::wstring& name) override
2822 out << name << L"\n\n";
2826 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2828 std::wstringstream result;
2829 result << L"200 " << help_command << L" OK\r\n";
2830 max_width_sink width;
2831 ctx.help_repo->help(tags, width);
2832 short_description_sink sink(width.max_width, result);
2833 sink.width = width.max_width;
2834 ctx.help_repo->help(tags, sink);
2836 return result.str();
2839 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2841 std::wstringstream result;
2842 result << L"201 " << help_command << L" OK\r\n";
2843 auto joined = boost::join(ctx.parameters, L" ");
2844 long_description_sink sink(result);
2845 ctx.help_repo->help(tags, joined, sink);
2847 return result.str();
2850 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2852 sink.short_description(L"Show online help for AMCP commands.");
2853 sink.syntax(LR"(HELP {[command:string]})");
2854 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2855 sink.example(L">> HELP", L"Shows a list of commands.");
2856 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2859 std::wstring help_command(command_context& ctx)
2861 if (ctx.parameters.size() == 0)
2862 return create_help_list(L"HELP", ctx, { L"AMCP" });
2864 return create_help_details(L"HELP", ctx, { L"AMCP" });
2867 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2869 sink.short_description(L"Show online help for producers.");
2870 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2871 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2872 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2873 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2876 std::wstring help_producer_command(command_context& ctx)
2878 if (ctx.parameters.size() == 0)
2879 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2881 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2884 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2886 sink.short_description(L"Show online help for consumers.");
2887 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2888 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2889 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2890 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2893 std::wstring help_consumer_command(command_context& ctx)
2895 if (ctx.parameters.size() == 0)
2896 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2898 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2901 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2903 sink.short_description(L"Disconnect the session.");
2904 sink.syntax(L"BYE");
2906 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2907 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2910 std::wstring bye_command(command_context& ctx)
2912 ctx.client->disconnect();
2916 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2918 sink.short_description(L"Shutdown the server.");
2919 sink.syntax(L"KILL");
2920 sink.para()->text(L"Shuts the server down.");
2923 std::wstring kill_command(command_context& ctx)
2925 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2926 return L"202 KILL OK\r\n";
2929 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2931 sink.short_description(L"Shutdown the server with restart exit code.");
2932 sink.syntax(L"RESTART");
2934 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2935 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2938 std::wstring restart_command(command_context& ctx)
2940 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2941 return L"202 RESTART OK\r\n";
2944 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2946 sink.short_description(L"Lock or unlock access to a channel.");
2947 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2948 sink.para()->text(L"Allows for exclusive access to a channel.");
2949 sink.para()->text(L"Examples:");
2950 sink.example(L">> LOCK 1 ACQUIRE secret");
2951 sink.example(L">> LOCK 1 RELEASE");
2952 sink.example(L">> LOCK 1 CLEAR");
2955 std::wstring lock_command(command_context& ctx)
2957 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2958 auto lock = ctx.channels.at(channel_index).lock;
2959 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2961 if (command == L"ACQUIRE")
2963 std::wstring lock_phrase = ctx.parameters.at(2);
2965 //TODO: read options
2967 //just lock one channel
2968 if (!lock->try_lock(lock_phrase, ctx.client))
2969 return L"503 LOCK ACQUIRE FAILED\r\n";
2971 return L"202 LOCK ACQUIRE OK\r\n";
2973 else if (command == L"RELEASE")
2975 lock->release_lock(ctx.client);
2976 return L"202 LOCK RELEASE OK\r\n";
2978 else if (command == L"CLEAR")
2980 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2981 std::wstring client_override_phrase;
2983 if (!override_phrase.empty())
2984 client_override_phrase = ctx.parameters.at(2);
2986 //just clear one channel
2987 if (client_override_phrase != override_phrase)
2988 return L"503 LOCK CLEAR FAILED\r\n";
2990 lock->clear_locks();
2992 return L"202 LOCK CLEAR OK\r\n";
2995 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2998 void req_describer(core::help_sink& sink, const core::help_repository& repo)
3000 sink.short_description(L"Perform any command with an additional request id identifying the response.");
3001 sink.syntax(L"REQ [request_id:string] COMMAND...");
3003 ->text(L"This special command modifies the AMCP protocol a little bit to prepend ")
3004 ->code(L"RES request_id")->text(L" to the response, in order to see what asynchronous response matches what request.");
3005 sink.para()->text(L"Examples:");
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