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>
42 #include <core/producer/cg_proxy.h>
43 #include <core/producer/frame_producer.h>
44 #include <core/help/help_repository.h>
45 #include <core/help/help_sink.h>
46 #include <core/help/util.h>
47 #include <core/video_format.h>
48 #include <core/producer/transition/transition_producer.h>
49 #include <core/frame/frame_transform.h>
50 #include <core/producer/stage.h>
51 #include <core/producer/layer.h>
52 #include <core/mixer/mixer.h>
53 #include <core/consumer/output.h>
54 #include <core/thumbnail_generator.h>
55 #include <core/producer/media_info/media_info.h>
56 #include <core/producer/media_info/media_info_repository.h>
57 #include <core/diagnostics/call_context.h>
58 #include <core/diagnostics/osd_graph.h>
59 #include <core/system_info_provider.h>
68 #include <boost/date_time/posix_time/posix_time.hpp>
69 #include <boost/lexical_cast.hpp>
70 #include <boost/algorithm/string.hpp>
71 #include <boost/filesystem.hpp>
72 #include <boost/filesystem/fstream.hpp>
73 #include <boost/regex.hpp>
74 #include <boost/property_tree/xml_parser.hpp>
75 #include <boost/locale.hpp>
76 #include <boost/range/adaptor/transformed.hpp>
77 #include <boost/range/algorithm/copy.hpp>
78 #include <boost/archive/iterators/base64_from_binary.hpp>
79 #include <boost/archive/iterators/insert_linebreaks.hpp>
80 #include <boost/archive/iterators/transform_width.hpp>
82 #include <tbb/concurrent_unordered_map.h>
86 102 [action] Information that [action] has happened
87 101 [action] Information that [action] has happened plus one row of data
89 202 [command] OK [command] has been executed
90 201 [command] OK [command] has been executed, plus one row of data
91 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
93 400 ERROR the command could not be understood
94 401 [command] ERROR invalid/missing channel
95 402 [command] ERROR parameter missing
96 403 [command] ERROR invalid parameter
97 404 [command] ERROR file not found
99 500 FAILED internal error
100 501 [command] FAILED internal error
101 502 [command] FAILED could not read file
102 503 [command] FAILED access denied
104 600 [command] FAILED [command] not implemented
107 namespace caspar { namespace protocol { namespace amcp {
109 using namespace core;
111 std::wstring read_file_base64(const boost::filesystem::path& file)
113 using namespace boost::archive::iterators;
115 boost::filesystem::ifstream filestream(file, std::ios::binary);
120 auto length = boost::filesystem::file_size(file);
121 std::vector<char> bytes;
122 bytes.resize(length);
123 filestream.read(bytes.data(), length);
125 std::string result(to_base64(bytes.data(), length));
126 return std::wstring(result.begin(), result.end());
129 std::wstring read_utf8_file(const boost::filesystem::path& file)
131 std::wstringstream result;
132 boost::filesystem::wifstream filestream(file);
139 result << filestream.rdbuf();
145 std::wstring read_latin1_file(const boost::filesystem::path& file)
147 boost::locale::generator gen;
148 gen.locale_cache_enabled(true);
149 gen.categories(boost::locale::codepage_facet);
151 std::stringstream result_stream;
152 boost::filesystem::ifstream filestream(file);
153 filestream.imbue(gen("en_US.ISO8859-1"));
158 result_stream << filestream.rdbuf();
161 std::string result = result_stream.str();
162 std::wstring widened_result;
164 // The first 255 codepoints in unicode is the same as in latin1
166 result | boost::adaptors::transformed(
167 [](char c) { return static_cast<unsigned char>(c); }),
168 std::back_inserter(widened_result));
170 return widened_result;
173 std::wstring read_file(const boost::filesystem::path& file)
175 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
177 if (!boost::filesystem::exists(file))
182 if (boost::filesystem::file_size(file) >= 3)
184 boost::filesystem::ifstream bom_stream(file);
187 bom_stream.read(header, 3);
190 if (std::memcmp(BOM, header, 3) == 0)
191 return read_utf8_file(file);
194 return read_latin1_file(file);
197 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
199 if (!boost::filesystem::is_regular_file(path))
202 auto media_info = media_info_repo->get(path.wstring());
207 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
209 auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
211 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
212 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
213 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
215 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
216 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
217 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
219 auto str = relativePath.replace_extension(L"").generic_wstring();
220 if (str[0] == '\\' || str[0] == '/')
221 str = std::wstring(str.begin() + 1, str.end());
223 return std::wstring()
225 + L"\" " + media_info->clip_type +
227 + L" " + writeTimeWStr +
228 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
229 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
233 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
235 std::wstringstream replyString;
236 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
237 replyString << MediaInfo(itr->path(), media_info_repo);
239 return boost::to_upper_copy(replyString.str());
242 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
244 std::wstringstream replyString;
246 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
248 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
250 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
252 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
253 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
254 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
256 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
257 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
259 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
261 auto dir = relativePath.parent_path();
262 auto file = boost::to_upper_copy(relativePath.filename().wstring());
263 relativePath = dir / file;
265 auto str = relativePath.replace_extension(L"").generic_wstring();
266 boost::trim_if(str, boost::is_any_of("\\/"));
268 replyString << L"\"" << str
269 << L"\" " << sizeWStr
270 << L" " << writeTimeWStr
274 return replyString.str();
277 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
279 return core::frame_producer_dependencies(
280 channel->frame_factory(),
281 cpplinq::from(ctx.channels)
282 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
284 channel->video_format_desc(),
285 ctx.producer_registry);
290 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
292 sink.short_description(L"Load a media file or resource in the background.");
293 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]})");
295 ->text(L"Loads a producer in the background and prepares it for playout. ")
296 ->text(L"If no layer is specified the default layer index will be used.");
298 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
299 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
301 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
302 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
304 ->code(L"loop")->text(L" will cause the clip to loop.");
306 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
308 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
310 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
311 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
312 sink.para()->text(L"Examples:");
313 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
314 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
315 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
316 sink.example(L">> LOADBG 1-0 MY_FILE");
318 L">> PLAY 1-1 MY_FILE\n"
319 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
320 L"To automatically fade out a layer after a video file has been played to the end");
322 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
324 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
325 ->code(L"filter")->text(L" command.");
328 std::wstring loadbg_command(command_context& ctx)
330 transition_info transitionInfo;
334 std::wstring message;
335 for (size_t n = 0; n < ctx.parameters.size(); ++n)
336 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
338 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)?.*)");
340 if (boost::regex_match(message, what, expr))
342 auto transition = what["TRANSITION"].str();
343 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
344 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
345 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
346 transitionInfo.tweener = tween;
348 if (transition == L"CUT")
349 transitionInfo.type = transition_type::cut;
350 else if (transition == L"MIX")
351 transitionInfo.type = transition_type::mix;
352 else if (transition == L"PUSH")
353 transitionInfo.type = transition_type::push;
354 else if (transition == L"SLIDE")
355 transitionInfo.type = transition_type::slide;
356 else if (transition == L"WIPE")
357 transitionInfo.type = transition_type::wipe;
359 if (direction == L"FROMLEFT")
360 transitionInfo.direction = transition_direction::from_left;
361 else if (direction == L"FROMRIGHT")
362 transitionInfo.direction = transition_direction::from_right;
363 else if (direction == L"LEFT")
364 transitionInfo.direction = transition_direction::from_right;
365 else if (direction == L"RIGHT")
366 transitionInfo.direction = transition_direction::from_left;
369 //Perform loading of the clip
370 core::diagnostics::scoped_call_context save;
371 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
372 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
374 auto channel = ctx.channel.channel;
375 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
377 if (pFP == frame_producer::empty())
378 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
380 bool auto_play = contains_param(L"AUTO", ctx.parameters);
382 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
384 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
386 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
388 return L"202 LOADBG OK\r\n";
391 void load_describer(core::help_sink& sink, const core::help_repository& repo)
393 sink.short_description(L"Load a media file or resource to the foreground.");
394 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
396 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
397 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
398 sink.para()->text(L"Examples:");
399 sink.example(L">> LOAD 1 MY_FILE");
400 sink.example(L">> LOAD 1-1 MY_FILE");
401 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
404 std::wstring load_command(command_context& ctx)
406 core::diagnostics::scoped_call_context save;
407 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
408 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
409 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
410 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
412 return L"202 LOAD OK\r\n";
415 void play_describer(core::help_sink& sink, const core::help_repository& repository)
417 sink.short_description(L"Play a media file or resource.");
418 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
420 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
421 ->text(L") is prepared, it will be executed.");
423 ->text(L"If additional parameters (see ")->see(L"LOADBG")
424 ->text(L") are provided then the provided clip will first be loaded to the background.");
425 sink.para()->text(L"Examples:");
426 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
427 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
428 sink.example(L">> PLAY 1-0 MY_FILE");
429 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
432 std::wstring play_command(command_context& ctx)
434 if (!ctx.parameters.empty())
437 ctx.channel.channel->stage().play(ctx.layer_index());
439 return L"202 PLAY OK\r\n";
442 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
444 sink.short_description(L"Pause playback of a layer.");
445 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
447 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
448 ->text(L" command can be used to resume playback again.");
449 sink.para()->text(L"Examples:");
450 sink.example(L">> PAUSE 1");
451 sink.example(L">> PAUSE 1-1");
454 std::wstring pause_command(command_context& ctx)
456 ctx.channel.channel->stage().pause(ctx.layer_index());
457 return L"202 PAUSE OK\r\n";
460 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
462 sink.short_description(L"Resume playback of a layer.");
463 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
465 ->text(L"Resumes playback of a foreground clip previously paused with the ")
466 ->see(L"PAUSE")->text(L" command.");
467 sink.para()->text(L"Examples:");
468 sink.example(L">> RESUME 1");
469 sink.example(L">> RESUME 1-1");
472 std::wstring resume_command(command_context& ctx)
474 ctx.channel.channel->stage().resume(ctx.layer_index());
475 return L"202 RESUME OK\r\n";
478 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
480 sink.short_description(L"Remove the foreground clip of a layer.");
481 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
483 ->text(L"Removes the foreground clip of the specified layer.");
484 sink.para()->text(L"Examples:");
485 sink.example(L">> STOP 1");
486 sink.example(L">> STOP 1-1");
489 std::wstring stop_command(command_context& ctx)
491 ctx.channel.channel->stage().stop(ctx.layer_index());
492 return L"202 STOP OK\r\n";
495 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
497 sink.short_description(L"Remove all clips of a layer or an entire channel.");
498 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
500 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
501 ->text(L"If no layer is specified then all layers in the specified ")
502 ->code(L"video_channel")->text(L" are cleared.");
503 sink.para()->text(L"Examples:");
504 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
505 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
508 std::wstring clear_command(command_context& ctx)
510 int index = ctx.layer_index(std::numeric_limits<int>::min());
511 if (index != std::numeric_limits<int>::min())
512 ctx.channel.channel->stage().clear(index);
514 ctx.channel.channel->stage().clear();
516 return L"202 CLEAR OK\r\n";
519 void call_describer(core::help_sink& sink, const core::help_repository& repo)
521 sink.short_description(L"Call a method on a producer.");
522 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
524 ->text(L"Calls method on the specified producer with the provided ")
525 ->code(L"param")->text(L" string.");
526 sink.para()->text(L"Examples:");
527 sink.example(L">> CALL 1 LOOP");
528 sink.example(L">> CALL 1-2 SEEK 25");
531 std::wstring call_command(command_context& ctx)
533 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
535 // TODO: because of std::async deferred timed waiting does not work
537 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
538 if (wait_res == std::future_status::timeout)
539 CASPAR_THROW_EXCEPTION(timed_out());*/
541 std::wstringstream replyString;
542 if (result.get().empty())
543 replyString << L"202 CALL OK\r\n";
545 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
547 return replyString.str();
550 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
552 sink.short_description(L"Swap layers between channels.");
553 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
555 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
556 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
557 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
558 sink.para()->text(L"Examples:");
559 sink.example(L">> SWAP 1 2");
560 sink.example(L">> SWAP 1-1 2-3");
561 sink.example(L">> SWAP 1-1 1-2");
562 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
565 std::wstring swap_command(command_context& ctx)
567 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
569 if (ctx.layer_index(-1) != -1)
571 std::vector<std::string> strs;
572 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
574 auto ch1 = ctx.channel.channel;
575 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
577 int l1 = ctx.layer_index();
578 int l2 = boost::lexical_cast<int>(strs.at(1));
580 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
584 auto ch1 = ctx.channel.channel;
585 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
586 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
589 return L"202 SWAP OK\r\n";
592 void add_describer(core::help_sink& sink, const core::help_repository& repo)
594 sink.short_description(L"Add a consumer to a video channel.");
595 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
597 ->text(L"Adds a consumer to the specified video channel. The string ")
598 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
599 ->text(L"If a successful match is found a consumer will be created and added to the ")
600 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
601 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
602 ->see(L"the CasparCG config file")->text(L".");
603 sink.para()->text(L"Examples:");
604 sink.example(L">> ADD 1 DECKLINK 1");
605 sink.example(L">> ADD 1 BLUEFISH 2");
606 sink.example(L">> ADD 1 SCREEN");
607 sink.example(L">> ADD 1 AUDIO");
608 sink.example(L">> ADD 1 IMAGE filename");
609 sink.example(L">> ADD 1 FILE filename.mov");
610 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
611 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
612 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
615 std::wstring add_command(command_context& ctx)
617 replace_placeholders(
618 L"<CLIENT_IP_ADDRESS>",
619 ctx.client->address(),
622 core::diagnostics::scoped_call_context save;
623 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
625 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
626 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
628 return L"202 ADD OK\r\n";
631 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
633 sink.short_description(L"Remove a consumer from a video channel.");
634 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
636 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
637 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
638 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
639 sink.para()->text(L"Examples:");
640 sink.example(L">> REMOVE 1 DECKLINK 1");
641 sink.example(L">> REMOVE 1 BLUEFISH 2");
642 sink.example(L">> REMOVE 1 SCREEN");
643 sink.example(L">> REMOVE 1 AUDIO");
644 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
647 std::wstring remove_command(command_context& ctx)
649 auto index = ctx.layer_index(std::numeric_limits<int>::min());
651 if (index == std::numeric_limits<int>::min())
653 replace_placeholders(
654 L"<CLIENT_IP_ADDRESS>",
655 ctx.client->address(),
658 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
661 ctx.channel.channel->output().remove(index);
663 return L"202 REMOVE OK\r\n";
666 void print_describer(core::help_sink& sink, const core::help_repository& repo)
668 sink.short_description(L"Take a snapshot of a channel.");
669 sink.syntax(L"PRINT [video_channel:int]");
671 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
672 ->code(L"media")->text(L" folder.");
673 sink.para()->text(L"Examples:");
674 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
677 std::wstring print_command(command_context& ctx)
679 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
681 return L"202 PRINT OK\r\n";
684 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
686 sink.short_description(L"Change the log level of the server.");
687 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
688 sink.para()->text(L"Changes the log level of the server.");
689 sink.para()->text(L"Examples:");
690 sink.example(L">> LOG LEVEL trace");
691 sink.example(L">> LOG LEVEL info");
694 std::wstring log_level_command(command_context& ctx)
696 log::set_log_level(ctx.parameters.at(0));
698 return L"202 LOG OK\r\n";
701 void set_describer(core::help_sink& sink, const core::help_repository& repo)
703 sink.short_description(L"Change the value of a channel variable.");
704 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
705 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
707 ->item(L"MODE", L"Changes the video format of the channel.");
708 sink.para()->text(L"Examples:");
709 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL");
712 std::wstring set_command(command_context& ctx)
714 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
715 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
719 auto format_desc = core::video_format_desc(value);
720 if (format_desc.format != core::video_format::invalid)
722 ctx.channel.channel->video_format_desc(format_desc);
723 return L"202 SET MODE OK\r\n";
726 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
729 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
732 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
734 sink.short_description(L"Store a dataset.");
735 sink.syntax(L"DATA STORE [name:string] [data:string]");
736 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
737 sink.para()->text(L"Directories will be created if they do not exist.");
738 sink.para()->text(L"Examples:");
739 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
740 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
743 std::wstring data_store_command(command_context& ctx)
745 std::wstring filename = env::data_folder();
746 filename.append(ctx.parameters[0]);
747 filename.append(L".ftd");
749 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
750 auto found_data_path = find_case_insensitive(data_path);
753 data_path = *found_data_path;
755 if (!boost::filesystem::exists(data_path))
756 boost::filesystem::create_directories(data_path);
758 auto found_filename = find_case_insensitive(filename);
761 filename = *found_filename; // Overwrite case insensitive.
763 boost::filesystem::wofstream datafile(filename);
765 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
767 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
768 datafile << ctx.parameters[1] << std::flush;
771 return L"202 DATA STORE OK\r\n";
774 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
776 sink.short_description(L"Retrieve a dataset.");
777 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
778 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
779 sink.para()->text(L"Examples:");
780 sink.example(L">> DATA RETRIEVE my_data");
781 sink.example(L">> DATA RETRIEVE Folder1/my_data");
784 std::wstring data_retrieve_command(command_context& ctx)
786 std::wstring filename = env::data_folder();
787 filename.append(ctx.parameters[0]);
788 filename.append(L".ftd");
790 std::wstring file_contents;
792 auto found_file = find_case_insensitive(filename);
795 file_contents = read_file(boost::filesystem::path(*found_file));
797 if (file_contents.empty())
798 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
800 std::wstringstream reply;
801 reply << L"201 DATA RETRIEVE OK\r\n";
803 std::wstringstream file_contents_stream(file_contents);
806 bool firstLine = true;
807 while (std::getline(file_contents_stream, line))
821 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
823 sink.short_description(L"List stored datasets.");
824 sink.syntax(L"DATA LIST");
825 sink.para()->text(L"Returns a list of all stored datasets.");
828 std::wstring data_list_command(command_context& ctx)
830 std::wstringstream replyString;
831 replyString << L"200 DATA LIST OK\r\n";
833 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
835 if (boost::filesystem::is_regular_file(itr->path()))
837 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
840 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
842 auto str = relativePath.replace_extension(L"").generic_wstring();
843 if (str[0] == L'\\' || str[0] == L'/')
844 str = std::wstring(str.begin() + 1, str.end());
846 replyString << str << L"\r\n";
850 replyString << L"\r\n";
852 return boost::to_upper_copy(replyString.str());
855 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
857 sink.short_description(L"Remove a stored dataset.");
858 sink.syntax(L"DATA REMOVE [name:string]");
859 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
860 sink.para()->text(L"Examples:");
861 sink.example(L">> DATA REMOVE my_data");
862 sink.example(L">> DATA REMOVE Folder1/my_data");
865 std::wstring data_remove_command(command_context& ctx)
867 std::wstring filename = env::data_folder();
868 filename.append(ctx.parameters[0]);
869 filename.append(L".ftd");
871 if (!boost::filesystem::exists(filename))
872 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
874 if (!boost::filesystem::remove(filename))
875 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
877 return L"201 DATA REMOVE OK\r\n";
880 // Template Graphics Commands
882 int get_and_validate_layer(const std::wstring& layerstring) {
883 int length = layerstring.length();
884 for (int i = 0; i < length; ++i) {
885 if (!std::isdigit(layerstring[i])) {
886 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
890 return boost::lexical_cast<int>(layerstring);
893 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
895 sink.short_description(L"Prepare a template for displaying.");
896 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
898 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
899 ->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.");
900 sink.para()->text(L"Examples:");
901 sink.example(L"CG 1 ADD 10 svtnews/info 1");
904 std::wstring cg_add_command(command_context& ctx)
906 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
908 int layer = get_and_validate_layer(ctx.parameters.at(0));
909 std::wstring label; //_parameters[2]
910 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
911 unsigned int dataIndex = 3;
913 if (ctx.parameters.at(2).length() > 1)
915 label = ctx.parameters.at(2);
918 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
919 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
922 { //read play-on-load-flag
923 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
926 const wchar_t* pDataString = 0;
927 std::wstring dataFromFile;
928 if (ctx.parameters.size() > dataIndex)
930 const std::wstring& dataString = ctx.parameters.at(dataIndex);
932 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
933 pDataString = dataString.c_str();
936 //The data is not an XML-string, it must be a filename
937 std::wstring filename = env::data_folder();
938 filename.append(dataString);
939 filename.append(L".ftd");
941 auto found_file = find_case_insensitive(filename);
945 dataFromFile = read_file(boost::filesystem::path(*found_file));
946 pDataString = dataFromFile.c_str();
951 auto filename = ctx.parameters.at(1);
952 auto proxy = ctx.cg_registry->get_or_create_proxy(
953 spl::make_shared_ptr(ctx.channel.channel),
954 get_producer_dependencies(ctx.channel.channel, ctx),
955 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
958 if (proxy == core::cg_proxy::empty())
959 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
961 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
963 return L"202 CG OK\r\n";
966 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
968 sink.short_description(L"Play and display a template.");
969 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
970 sink.para()->text(L"Plays and displays the template in the specified layer.");
971 sink.para()->text(L"Examples:");
972 sink.example(L"CG 1 PLAY 0");
975 std::wstring cg_play_command(command_context& ctx)
977 int layer = get_and_validate_layer(ctx.parameters.at(0));
978 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
980 return L"202 CG OK\r\n";
983 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
985 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
987 if (proxy == cg_proxy::empty())
988 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No CG proxy running on layer"));
993 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
995 sink.short_description(L"Stop and remove a template.");
996 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
998 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
999 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1000 sink.para()->text(L"Examples:");
1001 sink.example(L"CG 1 STOP 0");
1004 std::wstring cg_stop_command(command_context& ctx)
1006 int layer = get_and_validate_layer(ctx.parameters.at(0));
1007 get_expected_cg_proxy(ctx)->stop(layer, 0);
1009 return L"202 CG OK\r\n";
1012 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1014 sink.short_description(LR"(Trigger a "continue" in a template.)");
1015 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1017 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1018 ->text(L"This is used to control animations that has multiple discreet steps.");
1019 sink.para()->text(L"Examples:");
1020 sink.example(L"CG 1 NEXT 0");
1023 std::wstring cg_next_command(command_context& ctx)
1025 int layer = get_and_validate_layer(ctx.parameters.at(0));
1026 get_expected_cg_proxy(ctx)->next(layer);
1028 return L"202 CG OK\r\n";
1031 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1033 sink.short_description(L"Remove a template.");
1034 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1035 sink.para()->text(L"Removes the template from the specified layer.");
1036 sink.para()->text(L"Examples:");
1037 sink.example(L"CG 1 REMOVE 0");
1040 std::wstring cg_remove_command(command_context& ctx)
1042 int layer = get_and_validate_layer(ctx.parameters.at(0));
1043 get_expected_cg_proxy(ctx)->remove(layer);
1045 return L"202 CG OK\r\n";
1048 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1050 sink.short_description(L"Remove all templates on a video layer.");
1051 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1052 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1053 sink.para()->text(L"Examples:");
1054 sink.example(L"CG 1 CLEAR");
1057 std::wstring cg_clear_command(command_context& ctx)
1059 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1061 return L"202 CG OK\r\n";
1064 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1066 sink.short_description(L"Update a template with new data.");
1067 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1068 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.");
1071 std::wstring cg_update_command(command_context& ctx)
1073 int layer = get_and_validate_layer(ctx.parameters.at(0));
1075 std::wstring dataString = ctx.parameters.at(1);
1076 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1078 //The data is not XML or Json, it must be a filename
1079 std::wstring filename = env::data_folder();
1080 filename.append(dataString);
1081 filename.append(L".ftd");
1083 dataString = read_file(boost::filesystem::path(filename));
1086 get_expected_cg_proxy(ctx)->update(layer, dataString);
1088 return L"202 CG OK\r\n";
1091 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1093 sink.short_description(L"Invoke a method/label on a template.");
1094 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1095 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1096 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1099 std::wstring cg_invoke_command(command_context& ctx)
1101 std::wstringstream replyString;
1102 replyString << L"201 CG OK\r\n";
1103 int layer = get_and_validate_layer(ctx.parameters.at(0));
1104 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1105 replyString << result << L"\r\n";
1107 return replyString.str();
1110 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1112 sink.short_description(L"Get information about a running template or the template host.");
1113 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1114 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1115 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1118 std::wstring cg_info_command(command_context& ctx)
1120 std::wstringstream replyString;
1121 replyString << L"201 CG OK\r\n";
1123 if (ctx.parameters.empty())
1125 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1126 replyString << info << L"\r\n";
1130 int layer = get_and_validate_layer(ctx.parameters.at(0));
1131 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1133 replyString << desc << L"\r\n";
1136 return replyString.str();
1141 core::frame_transform get_current_transform(command_context& ctx)
1143 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1146 template<typename Func>
1147 std::wstring reply_value(command_context& ctx, const Func& extractor)
1149 auto value = extractor(get_current_transform(ctx));
1151 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1154 class transforms_applier
1156 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1158 std::vector<stage::transform_tuple_t> transforms_;
1159 command_context& ctx_;
1162 transforms_applier(command_context& ctx)
1165 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1168 ctx.parameters.pop_back();
1171 void add(stage::transform_tuple_t&& transform)
1173 transforms_.push_back(std::move(transform));
1176 void commit_deferred()
1178 auto& transforms = deferred_transforms_[ctx_.channel_index];
1179 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1187 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1188 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1191 ctx_.channel.channel->stage().apply_transforms(transforms_);
1194 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1196 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1198 sink.short_description(L"Let a layer act as alpha for the one obove.");
1199 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1201 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1202 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1203 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1204 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1205 ->text(L"instead it will be used as the key for the layer above.");
1206 sink.para()->text(L"Examples:");
1207 sink.example(L">> MIXER 1-0 KEYER 1");
1209 L">> MIXER 1-0 KEYER\n"
1210 L"<< 201 MIXER OK\n"
1211 L"<< 1", L"to retrieve the current state");
1214 std::wstring mixer_keyer_command(command_context& ctx)
1216 if (ctx.parameters.empty())
1217 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1219 transforms_applier transforms(ctx);
1220 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1221 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1223 transform.image_transform.is_key = value;
1228 return L"202 MIXER OK\r\n";
1231 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1233 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1235 sink.short_description(L"Enable chroma keying on a layer.");
1236 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1238 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1239 sink.para()->text(L"Examples:");
1240 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1241 sink.example(L">> MIXER 1-1 CHROMA none");
1243 L">> MIXER 1-1 BLEND\n"
1244 L"<< 201 MIXER OK\n"
1245 L"<< SCREEN", L"for getting the current blend mode");
1248 std::wstring mixer_chroma_command(command_context& ctx)
1250 if (ctx.parameters.empty())
1252 auto chroma = get_current_transform(ctx).image_transform.chroma;
1253 return L"201 MIXER OK\r\n"
1254 + core::get_chroma_mode(chroma.key) + L" "
1255 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1256 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1257 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1260 transforms_applier transforms(ctx);
1261 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1262 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1264 core::chroma chroma;
1265 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1267 if (chroma.key != core::chroma::type::none)
1269 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1270 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1271 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1274 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1276 transform.image_transform.chroma = chroma;
1278 }, duration, tween));
1281 return L"202 MIXER OK\r\n";
1284 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1286 sink.short_description(L"Set the blend mode for a layer.");
1287 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1289 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1290 ->text(L"If no argument is given the current blend mode is returned.");
1292 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1293 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1294 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1295 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1296 sink.para()->text(L"Examples:");
1297 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1299 L">> MIXER 1-1 BLEND\n"
1300 L"<< 201 MIXER OK\n"
1301 L"<< SCREEN", L"for getting the current blend mode");
1302 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1305 std::wstring mixer_blend_command(command_context& ctx)
1307 if (ctx.parameters.empty())
1308 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1310 transforms_applier transforms(ctx);
1311 auto value = get_blend_mode(ctx.parameters.at(0));
1312 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1314 transform.image_transform.blend_mode = value;
1319 return L"202 MIXER OK\r\n";
1322 template<typename Getter, typename Setter>
1323 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1325 if (ctx.parameters.empty())
1326 return reply_value(ctx, getter);
1328 transforms_applier transforms(ctx);
1329 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1330 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1331 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1333 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1335 setter(transform, value);
1337 }, duration, tween));
1340 return L"202 MIXER OK\r\n";
1343 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1345 sink.short_description(L"Change the opacity of a layer.");
1346 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1347 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1348 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1349 sink.para()->text(L"Examples:");
1350 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1352 L">> MIXER 1-0 OPACITY\n"
1353 L"<< 201 MIXER OK\n"
1354 L"<< 0.5", L"to retrieve the current opacity");
1357 std::wstring mixer_opacity_command(command_context& ctx)
1359 return single_double_animatable_mixer_command(
1361 [](const frame_transform& t) { return t.image_transform.opacity; },
1362 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1365 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1367 sink.short_description(L"Change the brightness of a layer.");
1368 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1369 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1370 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1371 sink.para()->text(L"Examples:");
1372 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1374 L">> MIXER 1-0 BRIGHTNESS\n"
1375 L"<< 201 MIXER OK\n"
1376 L"0.5", L"to retrieve the current brightness");
1379 std::wstring mixer_brightness_command(command_context& ctx)
1381 return single_double_animatable_mixer_command(
1383 [](const frame_transform& t) { return t.image_transform.brightness; },
1384 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1387 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1389 sink.short_description(L"Change the saturation of a layer.");
1390 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1391 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1392 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1393 sink.para()->text(L"Examples:");
1394 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1396 L">> MIXER 1-0 SATURATION\n"
1397 L"<< 201 MIXER OK\n"
1398 L"<< 0.5", L"to retrieve the current saturation");
1401 std::wstring mixer_saturation_command(command_context& ctx)
1403 return single_double_animatable_mixer_command(
1405 [](const frame_transform& t) { return t.image_transform.saturation; },
1406 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1409 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1411 sink.short_description(L"Change the contrast of a layer.");
1412 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1413 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1414 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1415 sink.para()->text(L"Examples:");
1416 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1418 L">> MIXER 1-0 CONTRAST\n"
1419 L"<< 201 MIXER OK\n"
1420 L"<< 0.5", L"to retrieve the current contrast");
1423 std::wstring mixer_contrast_command(command_context& ctx)
1425 return single_double_animatable_mixer_command(
1427 [](const frame_transform& t) { return t.image_transform.contrast; },
1428 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1431 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1433 sink.short_description(L"Adjust the video levels of a layer.");
1434 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);
1436 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1438 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1439 ->item(L"gamma", L"Adjusts the gamma of the image.")
1440 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1441 sink.para()->text(L"Examples:");
1442 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");
1443 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");
1444 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1446 L">> MIXER 1-10 LEVELS\n"
1447 L"<< 201 MIXER OK\n"
1448 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1451 std::wstring mixer_levels_command(command_context& ctx)
1453 if (ctx.parameters.empty())
1455 auto levels = get_current_transform(ctx).image_transform.levels;
1456 return L"201 MIXER OK\r\n"
1457 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1458 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1459 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1460 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1461 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1464 transforms_applier transforms(ctx);
1466 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1467 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1468 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1469 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1470 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1471 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1472 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1474 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1476 transform.image_transform.levels = value;
1478 }, duration, tween));
1481 return L"202 MIXER OK\r\n";
1484 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1486 sink.short_description(L"Change the fill position and scale of a layer.");
1487 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1489 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1490 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1491 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1492 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1494 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1495 ->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.");
1496 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1498 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1499 ->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, ")
1500 ->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");
1502 ->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.")
1503 ->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.")
1504 ->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.")
1505 ->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.");
1506 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1507 sink.para()->text(L"Examples:");
1508 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1510 L">> MIXER 1-0 FILL\n"
1511 L"<< 201 MIXER OK\n"
1512 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1515 std::wstring mixer_fill_command(command_context& ctx)
1517 if (ctx.parameters.empty())
1519 auto transform = get_current_transform(ctx).image_transform;
1520 auto translation = transform.fill_translation;
1521 auto scale = transform.fill_scale;
1522 return L"201 MIXER OK\r\n"
1523 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1524 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1525 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1526 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1529 transforms_applier transforms(ctx);
1530 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1531 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1532 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1533 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1534 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1535 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1537 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1539 transform.image_transform.fill_translation[0] = x;
1540 transform.image_transform.fill_translation[1] = y;
1541 transform.image_transform.fill_scale[0] = x_s;
1542 transform.image_transform.fill_scale[1] = y_s;
1544 }, duration, tween));
1547 return L"202 MIXER OK\r\n";
1550 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1552 sink.short_description(L"Change the clipping viewport of a layer.");
1553 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1555 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1556 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1557 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1559 ->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.")
1560 ->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.")
1561 ->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.")
1562 ->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.");
1563 sink.para()->text(L"Examples:");
1564 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1566 L">> MIXER 1-0 CLIP\n"
1567 L"<< 201 MIXER OK\n"
1568 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1571 std::wstring mixer_clip_command(command_context& ctx)
1573 if (ctx.parameters.empty())
1575 auto transform = get_current_transform(ctx).image_transform;
1576 auto translation = transform.clip_translation;
1577 auto scale = transform.clip_scale;
1579 return L"201 MIXER OK\r\n"
1580 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1581 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1582 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1583 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1586 transforms_applier transforms(ctx);
1587 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1588 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1589 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1590 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1591 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1592 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1594 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1596 transform.image_transform.clip_translation[0] = x;
1597 transform.image_transform.clip_translation[1] = y;
1598 transform.image_transform.clip_scale[0] = x_s;
1599 transform.image_transform.clip_scale[1] = y_s;
1601 }, duration, tween));
1604 return L"202 MIXER OK\r\n";
1607 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1609 sink.short_description(L"Change the anchor point of a layer.");
1610 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1611 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1613 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1614 ->text(L" will be done from.");
1616 ->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.")
1617 ->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.");
1618 sink.para()->text(L"Examples:");
1619 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1621 L">> MIXER 1-10 ANCHOR\n"
1622 L"<< 201 MIXER OK\n"
1623 L"<< 0.5 0.6", L"gets the anchor point");
1626 std::wstring mixer_anchor_command(command_context& ctx)
1628 if (ctx.parameters.empty())
1630 auto transform = get_current_transform(ctx).image_transform;
1631 auto anchor = transform.anchor;
1632 return L"201 MIXER OK\r\n"
1633 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1634 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1637 transforms_applier transforms(ctx);
1638 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1639 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1640 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1641 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1643 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1645 transform.image_transform.anchor[0] = x;
1646 transform.image_transform.anchor[1] = y;
1648 }, duration, tween));
1651 return L"202 MIXER OK\r\n";
1654 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1656 sink.short_description(L"Crop a layer.");
1657 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);
1659 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1660 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1661 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1663 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1664 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1665 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1666 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1667 sink.para()->text(L"Examples:");
1668 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");
1670 L">> MIXER 1-0 CROP\n"
1671 L"<< 201 MIXER OK\n"
1672 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1675 std::wstring mixer_crop_command(command_context& ctx)
1677 if (ctx.parameters.empty())
1679 auto crop = get_current_transform(ctx).image_transform.crop;
1680 return L"201 MIXER OK\r\n"
1681 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1682 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1683 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1684 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1687 transforms_applier transforms(ctx);
1688 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1689 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1690 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1691 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1692 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1693 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1695 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1697 transform.image_transform.crop.ul[0] = ul_x;
1698 transform.image_transform.crop.ul[1] = ul_y;
1699 transform.image_transform.crop.lr[0] = lr_x;
1700 transform.image_transform.crop.lr[1] = lr_y;
1702 }, duration, tween));
1705 return L"202 MIXER OK\r\n";
1708 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1710 sink.short_description(L"Rotate a layer.");
1711 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1713 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1714 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1715 sink.para()->text(L"Examples:");
1716 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1718 L">> MIXER 1-0 ROTATION\n"
1719 L"<< 201 MIXER OK\n"
1720 L"<< 45", L"to retrieve the current angle");
1723 std::wstring mixer_rotation_command(command_context& ctx)
1725 static const double PI = 3.141592653589793;
1727 return single_double_animatable_mixer_command(
1729 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1730 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1733 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1735 sink.short_description(L"Adjust the perspective transform of a layer.");
1736 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);
1738 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1740 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1741 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1742 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1743 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1744 sink.para()->text(L"Examples:");
1745 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1747 L">> MIXER 1-10 PERSPECTIVE\n"
1748 L"<< 201 MIXER OK\n"
1749 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1752 std::wstring mixer_perspective_command(command_context& ctx)
1754 if (ctx.parameters.empty())
1756 auto perspective = get_current_transform(ctx).image_transform.perspective;
1759 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1760 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1761 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1762 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1763 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1764 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1765 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1766 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1769 transforms_applier transforms(ctx);
1770 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1771 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1772 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1773 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1774 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1775 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1776 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1777 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1778 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1779 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1781 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1783 transform.image_transform.perspective.ul[0] = ul_x;
1784 transform.image_transform.perspective.ul[1] = ul_y;
1785 transform.image_transform.perspective.ur[0] = ur_x;
1786 transform.image_transform.perspective.ur[1] = ur_y;
1787 transform.image_transform.perspective.lr[0] = lr_x;
1788 transform.image_transform.perspective.lr[1] = lr_y;
1789 transform.image_transform.perspective.ll[0] = ll_x;
1790 transform.image_transform.perspective.ll[1] = ll_y;
1792 }, duration, tween));
1795 return L"202 MIXER OK\r\n";
1798 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1800 sink.short_description(L"Enable or disable mipmapping for a layer.");
1801 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1803 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1804 ->text(L"If no argument is given the current state is returned.");
1805 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1806 sink.para()->text(L"Examples:");
1807 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1809 L">> MIXER 1-10 MIPMAP\n"
1810 L"<< 201 MIXER OK\n"
1811 L"<< 1", L"for getting the current state");
1814 std::wstring mixer_mipmap_command(command_context& ctx)
1816 if (ctx.parameters.empty())
1817 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1819 transforms_applier transforms(ctx);
1820 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1821 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1823 transform.image_transform.use_mipmap = value;
1828 return L"202 MIXER OK\r\n";
1831 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1833 sink.short_description(L"Change the volume of a layer.");
1834 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1835 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1836 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1837 sink.para()->text(L"Examples:");
1838 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1839 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1841 L">> MIXER 1-0 VOLUME\n"
1842 L"<< 201 MIXER OK\n"
1843 L"<< 0.8", L"to retrieve the current volume");
1846 std::wstring mixer_volume_command(command_context& ctx)
1848 return single_double_animatable_mixer_command(
1850 [](const frame_transform& t) { return t.audio_transform.volume; },
1851 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1854 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1856 sink.short_description(L"Change the volume of an entire channel.");
1857 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1858 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1859 sink.para()->text(L"Examples:");
1860 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1861 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1862 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1865 std::wstring mixer_mastervolume_command(command_context& ctx)
1867 if (ctx.parameters.empty())
1869 auto volume = ctx.channel.channel->mixer().get_master_volume();
1870 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1873 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1874 ctx.channel.channel->mixer().set_master_volume(master_volume);
1876 return L"202 MIXER OK\r\n";
1879 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1881 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1882 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1883 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1884 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1885 sink.para()->text(L"Examples:");
1886 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1887 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1889 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1890 L"<< 201 MIXER OK\n"
1894 std::wstring mixer_straight_alpha_command(command_context& ctx)
1896 if (ctx.parameters.empty())
1898 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1899 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1902 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1903 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1905 return L"202 MIXER OK\r\n";
1908 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1910 sink.short_description(L"Create a grid of video layers.");
1911 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1913 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1914 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1915 sink.para()->text(L"Examples:");
1916 sink.example(L">> MIXER 1 GRID 2");
1919 std::wstring mixer_grid_command(command_context& ctx)
1921 transforms_applier transforms(ctx);
1922 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1923 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1924 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1925 double delta = 1.0 / static_cast<double>(n);
1926 for (int x = 0; x < n; ++x)
1928 for (int y = 0; y < n; ++y)
1930 int index = x + y*n + 1;
1931 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1933 transform.image_transform.fill_translation[0] = x*delta;
1934 transform.image_transform.fill_translation[1] = y*delta;
1935 transform.image_transform.fill_scale[0] = delta;
1936 transform.image_transform.fill_scale[1] = delta;
1937 transform.image_transform.clip_translation[0] = x*delta;
1938 transform.image_transform.clip_translation[1] = y*delta;
1939 transform.image_transform.clip_scale[0] = delta;
1940 transform.image_transform.clip_scale[1] = delta;
1942 }, duration, tween));
1947 return L"202 MIXER OK\r\n";
1950 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1952 sink.short_description(L"Commit all deferred mixer transforms.");
1953 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1954 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1955 sink.para()->text(L"Examples:");
1957 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1958 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1959 L">> MIXER 1 COMMIT");
1962 std::wstring mixer_commit_command(command_context& ctx)
1964 transforms_applier transforms(ctx);
1965 transforms.commit_deferred();
1967 return L"202 MIXER OK\r\n";
1970 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1972 sink.short_description(L"Clear all transformations on a channel or layer.");
1973 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1974 sink.para()->text(L"Clears all transformations on a channel or layer.");
1975 sink.para()->text(L"Examples:");
1976 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1977 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1980 std::wstring mixer_clear_command(command_context& ctx)
1982 int layer = ctx.layer_id;
1985 ctx.channel.channel->stage().clear_transforms();
1987 ctx.channel.channel->stage().clear_transforms(layer);
1989 return L"202 MIXER OK\r\n";
1992 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1994 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
1995 sink.syntax(L"CHANNEL_GRID");
1996 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
1998 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
1999 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2002 std::wstring channel_grid_command(command_context& ctx)
2005 auto self = ctx.channels.back();
2007 core::diagnostics::scoped_call_context save;
2008 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2010 std::vector<std::wstring> params;
2011 params.push_back(L"SCREEN");
2012 params.push_back(L"0");
2013 params.push_back(L"NAME");
2014 params.push_back(L"Channel Grid Window");
2015 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
2017 self.channel->output().add(screen);
2019 for (auto& channel : ctx.channels)
2021 if (channel.channel != self.channel)
2023 core::diagnostics::call_context::for_thread().layer = index;
2024 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2025 self.channel->stage().load(index, producer, false);
2026 self.channel->stage().play(index);
2031 auto num_channels = ctx.channels.size() - 1;
2032 int square_side_length = std::ceil(std::sqrt(num_channels));
2034 ctx.channel_index = self.channel->index();
2036 ctx.parameters.clear();
2037 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2038 mixer_grid_command(ctx);
2040 return L"202 CHANNEL_GRID OK\r\n";
2043 // Thumbnail Commands
2045 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2047 sink.short_description(L"List all thumbnails.");
2048 sink.syntax(L"THUMBNAIL LIST");
2049 sink.para()->text(L"Lists all thumbnails.");
2050 sink.para()->text(L"Examples:");
2052 L">> THUMBNAIL LIST\n"
2053 L"<< 200 THUMBNAIL LIST OK\n"
2054 L"<< \"AMB\" 20130301T124409 1149\n"
2055 L"<< \"foo/bar\" 20130523T234001 244");
2058 std::wstring thumbnail_list_command(command_context& ctx)
2060 std::wstringstream replyString;
2061 replyString << L"200 THUMBNAIL LIST OK\r\n";
2063 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2065 if (boost::filesystem::is_regular_file(itr->path()))
2067 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2070 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
2072 auto str = relativePath.replace_extension(L"").generic_wstring();
2073 if (str[0] == '\\' || str[0] == '/')
2074 str = std::wstring(str.begin() + 1, str.end());
2076 auto mtime = boost::filesystem::last_write_time(itr->path());
2077 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2078 auto file_size = boost::filesystem::file_size(itr->path());
2080 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2084 replyString << L"\r\n";
2086 return boost::to_upper_copy(replyString.str());
2089 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2091 sink.short_description(L"Retrieve a thumbnail.");
2092 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2093 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2094 sink.para()->text(L"Examples:");
2096 L">> THUMBNAIL RETRIEVE foo/bar\n"
2097 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2098 L"<< ...base64 data...");
2101 std::wstring thumbnail_retrieve_command(command_context& ctx)
2103 std::wstring filename = env::thumbnails_folder();
2104 filename.append(ctx.parameters.at(0));
2105 filename.append(L".png");
2107 std::wstring file_contents;
2109 auto found_file = find_case_insensitive(filename);
2112 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2114 if (file_contents.empty())
2115 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2117 std::wstringstream reply;
2119 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2120 reply << file_contents;
2125 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2127 sink.short_description(L"Regenerate a thumbnail.");
2128 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2129 sink.para()->text(L"Regenerates a thumbnail.");
2132 std::wstring thumbnail_generate_command(command_context& ctx)
2136 ctx.thumb_gen->generate(ctx.parameters.at(0));
2137 return L"202 THUMBNAIL GENERATE OK\r\n";
2140 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2143 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2145 sink.short_description(L"Regenerate all thumbnails.");
2146 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2147 sink.para()->text(L"Regenerates all thumbnails.");
2150 std::wstring thumbnail_generateall_command(command_context& ctx)
2154 ctx.thumb_gen->generate_all();
2155 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2158 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2163 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2165 sink.short_description(L"Get information about a media file.");
2166 sink.syntax(L"CINF [filename:string]");
2167 sink.para()->text(L"Returns information about a media file.");
2170 std::wstring cinf_command(command_context& ctx)
2173 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2175 auto path = itr->path();
2176 auto file = path.replace_extension(L"").filename().wstring();
2177 if (boost::iequals(file, ctx.parameters.at(0)))
2178 info += MediaInfo(itr->path(), ctx.media_info_repo);
2182 CASPAR_THROW_EXCEPTION(file_not_found());
2184 std::wstringstream replyString;
2185 replyString << L"200 CINF OK\r\n";
2186 replyString << info << "\r\n";
2188 return replyString.str();
2191 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2193 sink.short_description(L"List all media files.");
2194 sink.syntax(L"CLS");
2196 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2197 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2200 std::wstring cls_command(command_context& ctx)
2202 std::wstringstream replyString;
2203 replyString << L"200 CLS OK\r\n";
2204 replyString << ListMedia(ctx.media_info_repo);
2205 replyString << L"\r\n";
2206 return boost::to_upper_copy(replyString.str());
2209 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2211 sink.short_description(L"List all templates.");
2212 sink.syntax(L"TLS");
2214 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2215 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2218 std::wstring tls_command(command_context& ctx)
2220 std::wstringstream replyString;
2221 replyString << L"200 TLS OK\r\n";
2223 replyString << ListTemplates(ctx.cg_registry);
2224 replyString << L"\r\n";
2226 return replyString.str();
2229 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2231 sink.short_description(L"Get version information.");
2232 sink.syntax(L"VERSION {[component:string]}");
2233 sink.para()->text(L"Returns the version of specified component.");
2234 sink.para()->text(L"Examples:");
2237 L"<< 201 VERSION OK\n"
2238 L"<< 2.1.0.f207a33 STABLE");
2240 L">> VERSION SERVER\n"
2241 L"<< 201 VERSION OK\n"
2242 L"<< 2.1.0.f207a33 STABLE");
2244 L">> VERSION FLASH\n"
2245 L"<< 201 VERSION OK\n"
2249 std::wstring version_command(command_context& ctx)
2251 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2253 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2255 return L"201 VERSION OK\r\n" + version + L"\r\n";
2258 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2261 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2263 sink.short_description(L"Get a list of the available channels.");
2264 sink.syntax(L"INFO");
2265 sink.para()->text(L"Retrieves a list of the available channels.");
2269 L"<< 1 720p5000 PLAYING\n"
2270 L"<< 2 PAL PLAYING");
2273 std::wstring info_command(command_context& ctx)
2275 std::wstringstream replyString;
2276 // This is needed for backwards compatibility with old clients
2277 replyString << L"200 INFO OK\r\n";
2278 for (size_t n = 0; n < ctx.channels.size(); ++n)
2279 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2280 replyString << L"\r\n";
2281 return replyString.str();
2284 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2286 std::wstringstream replyString;
2288 if (command.empty())
2289 replyString << L"201 INFO OK\r\n";
2291 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2293 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2294 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2295 replyString << L"\r\n";
2296 return replyString.str();
2299 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2301 sink.short_description(L"Get information about a channel or a layer.");
2302 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2303 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2304 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2307 std::wstring info_channel_command(command_context& ctx)
2309 boost::property_tree::wptree info;
2310 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2312 if (layer == std::numeric_limits<int>::min())
2314 info.add_child(L"channel", ctx.channel.channel->info())
2315 .add(L"index", ctx.channel_index);
2319 if (ctx.parameters.size() >= 1)
2321 if (boost::iequals(ctx.parameters.at(0), L"B"))
2322 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2324 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2328 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2332 return create_info_xml_reply(info);
2335 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2337 sink.short_description(L"Get information about a template.");
2338 sink.syntax(L"INFO TEMPLATE [template:string]");
2339 sink.para()->text(L"Gets information about the specified template.");
2342 std::wstring info_template_command(command_context& ctx)
2344 auto filename = ctx.parameters.at(0);
2346 std::wstringstream str;
2347 str << u16(ctx.cg_registry->read_meta_info(filename));
2348 boost::property_tree::wptree info;
2349 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2351 return create_info_xml_reply(info, L"TEMPLATE");
2354 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2356 sink.short_description(L"Get the contents of the configuration used.");
2357 sink.syntax(L"INFO CONFIG");
2358 sink.para()->text(L"Gets the contents of the configuration used.");
2361 std::wstring info_config_command(command_context& ctx)
2363 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2366 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2368 sink.short_description(L"Get information about the paths used.");
2369 sink.syntax(L"INFO PATHS");
2370 sink.para()->text(L"Gets information about the paths used.");
2373 std::wstring info_paths_command(command_context& ctx)
2375 boost::property_tree::wptree info;
2376 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2377 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2379 return create_info_xml_reply(info, L"PATHS");
2382 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2384 sink.short_description(L"Get system information.");
2385 sink.syntax(L"INFO SYSTEM");
2386 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2389 std::wstring info_system_command(command_context& ctx)
2391 boost::property_tree::wptree info;
2393 info.add(L"system.name", caspar::system_product_name());
2394 info.add(L"system.os.description", caspar::os_description());
2395 info.add(L"system.cpu", caspar::cpu_info());
2397 ctx.system_info_repo->fill_information(info);
2399 return create_info_xml_reply(info, L"SYSTEM");
2402 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2404 sink.short_description(L"Get detailed information about all channels.");
2405 sink.syntax(L"INFO SERVER");
2406 sink.para()->text(L"Gets detailed information about all channels.");
2409 std::wstring info_server_command(command_context& ctx)
2411 boost::property_tree::wptree info;
2414 for (auto& channel : ctx.channels)
2415 info.add_child(L"channels.channel", channel.channel->info())
2416 .add(L"index", ++index);
2418 return create_info_xml_reply(info, L"SERVER");
2421 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2423 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2424 sink.syntax(L"INFO QUEUES");
2425 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2428 std::wstring info_queues_command(command_context& ctx)
2430 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2433 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2435 sink.short_description(L"Lists all known threads in the server.");
2436 sink.syntax(L"INFO THREADS");
2437 sink.para()->text(L"Lists all known threads in the server.");
2440 std::wstring info_threads_command(command_context& ctx)
2442 std::wstringstream replyString;
2443 replyString << L"200 INFO THREADS OK\r\n";
2445 for (auto& thread : get_thread_infos())
2447 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2450 replyString << L"\r\n";
2451 return replyString.str();
2454 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2456 sink.short_description(L"Get the current delay on a channel or a layer.");
2457 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2458 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2461 std::wstring info_delay_command(command_context& ctx)
2463 boost::property_tree::wptree info;
2464 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2466 if (layer == std::numeric_limits<int>::min())
2467 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2469 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2470 .add(L"index", layer);
2472 return create_info_xml_reply(info, L"DELAY");
2475 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2477 sink.short_description(L"Open the diagnostics window.");
2478 sink.syntax(L"DIAG");
2479 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2482 std::wstring diag_command(command_context& ctx)
2484 core::diagnostics::osd::show_graphs(true);
2486 return L"202 DIAG OK\r\n";
2489 static const int WIDTH = 80;
2491 struct max_width_sink : public core::help_sink
2493 std::size_t max_width = 0;
2495 void begin_item(const std::wstring& name) override
2497 max_width = std::max(name.length(), max_width);
2501 struct short_description_sink : public core::help_sink
2504 std::wstringstream& out;
2506 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2508 void begin_item(const std::wstring& name) override
2510 out << std::left << std::setw(width + 1) << name;
2513 void short_description(const std::wstring& short_description) override
2515 out << short_description << L"\r\n";
2519 struct simple_paragraph_builder : core::paragraph_builder
2521 std::wostringstream out;
2522 std::wstringstream& commit_to;
2524 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2525 ~simple_paragraph_builder()
2527 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2529 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2531 out << std::move(text);
2532 return shared_from_this();
2534 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2535 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2536 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2539 struct simple_definition_list_builder : core::definition_list_builder
2541 std::wstringstream& out;
2543 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2544 ~simple_definition_list_builder()
2549 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2551 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2552 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2553 return shared_from_this();
2557 struct long_description_sink : public core::help_sink
2559 std::wstringstream& out;
2561 long_description_sink(std::wstringstream& out) : out(out) { }
2563 void syntax(const std::wstring& syntax) override
2565 out << L"Syntax:\n";
2566 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2569 spl::shared_ptr<core::paragraph_builder> para() override
2571 return spl::make_shared<simple_paragraph_builder>(out);
2574 spl::shared_ptr<core::definition_list_builder> definitions() override
2576 return spl::make_shared<simple_definition_list_builder>(out);
2579 void example(const std::wstring& code, const std::wstring& caption) override
2581 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2583 if (!caption.empty())
2584 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2589 void begin_item(const std::wstring& name) override
2591 out << name << L"\n\n";
2595 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2597 std::wstringstream result;
2598 result << L"200 " << help_command << L" OK\r\n";
2599 max_width_sink width;
2600 ctx.help_repo->help(tags, width);
2601 short_description_sink sink(width.max_width, result);
2602 sink.width = width.max_width;
2603 ctx.help_repo->help(tags, sink);
2605 return result.str();
2608 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2610 std::wstringstream result;
2611 result << L"201 " << help_command << L" OK\r\n";
2612 auto joined = boost::join(ctx.parameters, L" ");
2613 long_description_sink sink(result);
2614 ctx.help_repo->help(tags, joined, sink);
2616 return result.str();
2619 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2621 sink.short_description(L"Show online help for AMCP commands.");
2622 sink.syntax(LR"(HELP {[command:string]})");
2623 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2624 sink.example(L">> HELP", L"Shows a list of commands.");
2625 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2628 std::wstring help_command(command_context& ctx)
2630 if (ctx.parameters.size() == 0)
2631 return create_help_list(L"HELP", ctx, { L"AMCP" });
2633 return create_help_details(L"HELP", ctx, { L"AMCP" });
2636 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2638 sink.short_description(L"Show online help for producers.");
2639 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2640 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2641 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2642 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2645 std::wstring help_producer_command(command_context& ctx)
2647 if (ctx.parameters.size() == 0)
2648 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2650 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2653 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2655 sink.short_description(L"Show online help for consumers.");
2656 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2657 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2658 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2659 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2662 std::wstring help_consumer_command(command_context& ctx)
2664 if (ctx.parameters.size() == 0)
2665 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2667 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2670 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2672 sink.short_description(L"Disconnect the session.");
2673 sink.syntax(L"BYE");
2675 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2676 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2679 std::wstring bye_command(command_context& ctx)
2681 ctx.client->disconnect();
2685 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2687 sink.short_description(L"Shutdown the server.");
2688 sink.syntax(L"KILL");
2689 sink.para()->text(L"Shuts the server down.");
2692 std::wstring kill_command(command_context& ctx)
2694 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2695 return L"202 KILL OK\r\n";
2698 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2700 sink.short_description(L"Shutdown the server with restart exit code.");
2701 sink.syntax(L"RESTART");
2703 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2704 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2707 std::wstring restart_command(command_context& ctx)
2709 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2710 return L"202 RESTART OK\r\n";
2713 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2715 sink.short_description(L"Lock or unlock access to a channel.");
2716 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2717 sink.para()->text(L"Allows for exclusive access to a channel.");
2718 sink.para()->text(L"Examples:");
2719 sink.example(L"LOCK 1 ACQUIRE secret");
2720 sink.example(L"LOCK 1 RELEASE");
2721 sink.example(L"LOCK 1 CLEAR");
2724 std::wstring lock_command(command_context& ctx)
2726 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2727 auto lock = ctx.channels.at(channel_index).lock;
2728 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2730 if (command == L"ACQUIRE")
2732 std::wstring lock_phrase = ctx.parameters.at(2);
2734 //TODO: read options
2736 //just lock one channel
2737 if (!lock->try_lock(lock_phrase, ctx.client))
2738 return L"503 LOCK ACQUIRE FAILED\r\n";
2740 return L"202 LOCK ACQUIRE OK\r\n";
2742 else if (command == L"RELEASE")
2744 lock->release_lock(ctx.client);
2745 return L"202 LOCK RELEASE OK\r\n";
2747 else if (command == L"CLEAR")
2749 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2750 std::wstring client_override_phrase;
2752 if (!override_phrase.empty())
2753 client_override_phrase = ctx.parameters.at(2);
2755 //just clear one channel
2756 if (client_override_phrase != override_phrase)
2757 return L"503 LOCK CLEAR FAILED\r\n";
2759 lock->clear_locks();
2761 return L"202 LOCK CLEAR OK\r\n";
2764 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2767 void register_commands(amcp_command_repository& repo)
2769 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2770 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2771 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2772 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2773 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2774 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2775 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2776 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2777 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2778 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2779 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2780 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2781 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2782 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2783 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2785 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2786 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2787 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2788 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2790 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2791 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2792 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2793 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2794 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2795 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2796 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2797 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2798 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2800 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2801 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2802 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2803 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2804 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2805 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2806 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2807 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2808 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2809 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2810 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2811 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2812 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2813 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2814 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2815 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2816 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2817 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2818 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2819 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2820 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2821 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2823 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2824 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2825 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2826 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2828 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2829 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2830 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2831 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2832 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2833 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2834 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2835 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2836 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2837 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2838 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2839 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2840 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2841 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2842 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2843 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2844 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2845 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2846 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2847 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2848 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2852 }} //namespace caspar