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"
32 #include <common/env.h>
34 #include <common/log.h>
35 #include <common/param.h>
36 #include <common/os/system_info.h>
37 #include <common/os/filesystem.h>
38 #include <common/base64.h>
40 #include <core/producer/cg_proxy.h>
41 #include <core/producer/frame_producer.h>
42 #include <core/help/help_repository.h>
43 #include <core/help/help_sink.h>
44 #include <core/video_format.h>
45 #include <core/producer/transition/transition_producer.h>
46 #include <core/frame/frame_transform.h>
47 #include <core/producer/stage.h>
48 #include <core/producer/layer.h>
49 #include <core/mixer/mixer.h>
50 #include <core/consumer/output.h>
51 #include <core/thumbnail_generator.h>
52 #include <core/producer/media_info/media_info.h>
53 #include <core/producer/media_info/media_info_repository.h>
54 #include <core/diagnostics/call_context.h>
55 #include <core/diagnostics/osd_graph.h>
56 #include <core/system_info_provider.h>
65 #include <boost/date_time/posix_time/posix_time.hpp>
66 #include <boost/lexical_cast.hpp>
67 #include <boost/algorithm/string.hpp>
68 #include <boost/filesystem.hpp>
69 #include <boost/filesystem/fstream.hpp>
70 #include <boost/regex.hpp>
71 #include <boost/property_tree/xml_parser.hpp>
72 #include <boost/locale.hpp>
73 #include <boost/range/adaptor/transformed.hpp>
74 #include <boost/range/algorithm/copy.hpp>
75 #include <boost/archive/iterators/base64_from_binary.hpp>
76 #include <boost/archive/iterators/insert_linebreaks.hpp>
77 #include <boost/archive/iterators/transform_width.hpp>
79 #include <tbb/concurrent_unordered_map.h>
83 102 [action] Information that [action] has happened
84 101 [action] Information that [action] has happened plus one row of data
86 202 [command] OK [command] has been executed
87 201 [command] OK [command] has been executed, plus one row of data
88 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
90 400 ERROR the command could not be understood
91 401 [command] ERROR invalid/missing channel
92 402 [command] ERROR parameter missing
93 403 [command] ERROR invalid parameter
94 404 [command] ERROR file not found
96 500 FAILED internal error
97 501 [command] FAILED internal error
98 502 [command] FAILED could not read file
99 503 [command] FAILED access denied
101 600 [command] FAILED [command] not implemented
104 namespace caspar { namespace protocol { namespace amcp {
106 using namespace core;
108 std::wstring read_file_base64(const boost::filesystem::path& file)
110 using namespace boost::archive::iterators;
112 boost::filesystem::ifstream filestream(file, std::ios::binary);
117 auto length = boost::filesystem::file_size(file);
118 std::vector<char> bytes;
119 bytes.resize(length);
120 filestream.read(bytes.data(), length);
122 std::string result(to_base64(bytes.data(), length));
123 return std::wstring(result.begin(), result.end());
126 std::wstring read_utf8_file(const boost::filesystem::path& file)
128 std::wstringstream result;
129 boost::filesystem::wifstream filestream(file);
136 result << filestream.rdbuf();
142 std::wstring read_latin1_file(const boost::filesystem::path& file)
144 boost::locale::generator gen;
145 gen.locale_cache_enabled(true);
146 gen.categories(boost::locale::codepage_facet);
148 std::stringstream result_stream;
149 boost::filesystem::ifstream filestream(file);
150 filestream.imbue(gen("en_US.ISO8859-1"));
155 result_stream << filestream.rdbuf();
158 std::string result = result_stream.str();
159 std::wstring widened_result;
161 // The first 255 codepoints in unicode is the same as in latin1
163 result | boost::adaptors::transformed(
164 [](char c) { return static_cast<unsigned char>(c); }),
165 std::back_inserter(widened_result));
167 return widened_result;
170 std::wstring read_file(const boost::filesystem::path& file)
172 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
174 if (!boost::filesystem::exists(file))
179 if (boost::filesystem::file_size(file) >= 3)
181 boost::filesystem::ifstream bom_stream(file);
184 bom_stream.read(header, 3);
187 if (std::memcmp(BOM, header, 3) == 0)
188 return read_utf8_file(file);
191 return read_latin1_file(file);
194 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
196 if (!boost::filesystem::is_regular_file(path))
199 auto media_info = media_info_repo->get(path.wstring());
204 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
206 auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
208 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
209 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
210 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
212 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
213 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
214 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
216 auto str = relativePath.replace_extension(L"").generic_wstring();
217 if (str[0] == '\\' || str[0] == '/')
218 str = std::wstring(str.begin() + 1, str.end());
220 return std::wstring()
222 + L"\" " + media_info->clip_type +
224 + L" " + writeTimeWStr +
225 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
226 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
230 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
232 std::wstringstream replyString;
233 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
234 replyString << MediaInfo(itr->path(), media_info_repo);
236 return boost::to_upper_copy(replyString.str());
239 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
241 std::wstringstream replyString;
243 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
245 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
247 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
249 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
250 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
251 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
253 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
254 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
256 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
258 auto dir = relativePath.parent_path();
259 auto file = boost::to_upper_copy(relativePath.filename().wstring());
260 relativePath = dir / file;
262 auto str = relativePath.replace_extension(L"").generic_wstring();
263 boost::trim_if(str, boost::is_any_of("\\/"));
265 replyString << L"\"" << str
266 << L"\" " << sizeWStr
267 << L" " << writeTimeWStr
271 return replyString.str();
274 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
276 return core::frame_producer_dependencies(
277 channel->frame_factory(),
278 cpplinq::from(ctx.channels)
279 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
281 channel->video_format_desc());
286 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
288 sink.short_description(L"Load a media file or resource in the background.");
289 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]})");
291 ->text(L"Loads a producer in the background and prepares it for playout. ")
292 ->text(L"If no layer is specified the default layer index will be used.");
294 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
295 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
297 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
298 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
300 ->code(L"loop")->text(L" will cause the clip to loop.");
302 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
304 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
306 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
307 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
308 sink.para()->text(L"Examples:");
309 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
310 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
311 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
312 sink.example(L">> LOADBG 1-0 MY_FILE");
314 L">> PLAY 1-1 MY_FILE\n"
315 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
316 L"To automatically fade out a layer after a video file has been played to the end");
318 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
320 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
321 ->code(L"filter")->text(L" command.");
324 std::wstring loadbg_command(command_context& ctx)
326 transition_info transitionInfo;
330 std::wstring message;
331 for (size_t n = 0; n < ctx.parameters.size(); ++n)
332 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
334 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)?.*)");
336 if (boost::regex_match(message, what, expr))
338 auto transition = what["TRANSITION"].str();
339 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
340 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
341 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
342 transitionInfo.tweener = tween;
344 if (transition == L"CUT")
345 transitionInfo.type = transition_type::cut;
346 else if (transition == L"MIX")
347 transitionInfo.type = transition_type::mix;
348 else if (transition == L"PUSH")
349 transitionInfo.type = transition_type::push;
350 else if (transition == L"SLIDE")
351 transitionInfo.type = transition_type::slide;
352 else if (transition == L"WIPE")
353 transitionInfo.type = transition_type::wipe;
355 if (direction == L"FROMLEFT")
356 transitionInfo.direction = transition_direction::from_left;
357 else if (direction == L"FROMRIGHT")
358 transitionInfo.direction = transition_direction::from_right;
359 else if (direction == L"LEFT")
360 transitionInfo.direction = transition_direction::from_right;
361 else if (direction == L"RIGHT")
362 transitionInfo.direction = transition_direction::from_left;
365 //Perform loading of the clip
366 core::diagnostics::scoped_call_context save;
367 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
368 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
370 auto channel = ctx.channel.channel;
371 auto pFP = create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
373 if (pFP == frame_producer::empty())
374 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
376 bool auto_play = contains_param(L"AUTO", ctx.parameters);
378 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
380 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
382 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
384 return L"202 LOADBG OK\r\n";
387 void load_describer(core::help_sink& sink, const core::help_repository& repo)
389 sink.short_description(L"Load a media file or resource to the foreground.");
390 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
392 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
393 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
394 sink.para()->text(L"Examples:");
395 sink.example(L">> LOAD 1 MY_FILE");
396 sink.example(L">> LOAD 1-1 MY_FILE");
397 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
400 std::wstring load_command(command_context& ctx)
402 core::diagnostics::scoped_call_context save;
403 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
404 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
405 auto pFP = create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
406 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
408 return L"202 LOAD OK\r\n";
411 void play_describer(core::help_sink& sink, const core::help_repository& repository)
413 sink.short_description(L"Play a media file or resource.");
414 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
416 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
417 ->text(L") is prepared, it will be executed.");
419 ->text(L"If additional parameters (see ")->see(L"LOADBG")
420 ->text(L") are provided then the provided clip will first be loaded to the background.");
421 sink.para()->text(L"Examples:");
422 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
423 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
424 sink.example(L">> PLAY 1-0 MY_FILE");
425 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
428 std::wstring play_command(command_context& ctx)
430 if (!ctx.parameters.empty())
433 ctx.channel.channel->stage().play(ctx.layer_index());
435 return L"202 PLAY OK\r\n";
438 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
440 sink.short_description(L"Pause playback of a layer.");
441 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
443 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
444 ->text(L" command can be used to resume playback again.");
445 sink.para()->text(L"Examples:");
446 sink.example(L">> PAUSE 1");
447 sink.example(L">> PAUSE 1-1");
450 std::wstring pause_command(command_context& ctx)
452 ctx.channel.channel->stage().pause(ctx.layer_index());
453 return L"202 PAUSE OK\r\n";
456 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
458 sink.short_description(L"Resume playback of a layer.");
459 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
461 ->text(L"Resumes playback of a foreground clip previously paused with the ")
462 ->see(L"PAUSE")->text(L" command.");
463 sink.para()->text(L"Examples:");
464 sink.example(L">> RESUME 1");
465 sink.example(L">> RESUME 1-1");
468 std::wstring resume_command(command_context& ctx)
470 ctx.channel.channel->stage().resume(ctx.layer_index());
471 return L"202 RESUME OK\r\n";
474 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
476 sink.short_description(L"Remove the foreground clip of a layer.");
477 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
479 ->text(L"Removes the foreground clip of the specified layer.");
480 sink.para()->text(L"Examples:");
481 sink.example(L">> STOP 1");
482 sink.example(L">> STOP 1-1");
485 std::wstring stop_command(command_context& ctx)
487 ctx.channel.channel->stage().stop(ctx.layer_index());
488 return L"202 STOP OK\r\n";
491 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
493 sink.short_description(L"Remove all clips of a layer or an entire channel.");
494 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
496 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
497 ->text(L"If no layer is specified then all layers in the specified ")
498 ->code(L"video_channel")->text(L" are cleared.");
499 sink.para()->text(L"Examples:");
500 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
501 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
504 std::wstring clear_command(command_context& ctx)
506 int index = ctx.layer_index(std::numeric_limits<int>::min());
507 if (index != std::numeric_limits<int>::min())
508 ctx.channel.channel->stage().clear(index);
510 ctx.channel.channel->stage().clear();
512 return L"202 CLEAR OK\r\n";
515 void call_describer(core::help_sink& sink, const core::help_repository& repo)
517 sink.short_description(L"Call a method on a producer.");
518 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
520 ->text(L"Calls method on the specified producer with the provided ")
521 ->code(L"param")->text(L" string.");
522 sink.para()->text(L"Examples:");
523 sink.example(L">> CALL 1 LOOP");
524 sink.example(L">> CALL 1-2 SEEK 25");
527 std::wstring call_command(command_context& ctx)
529 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
531 // TODO: because of std::async deferred timed waiting does not work
533 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
534 if (wait_res == std::future_status::timeout)
535 CASPAR_THROW_EXCEPTION(timed_out());*/
537 std::wstringstream replyString;
538 if (result.get().empty())
539 replyString << L"202 CALL OK\r\n";
541 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
543 return replyString.str();
546 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
548 sink.short_description(L"Swap layers between channels.");
549 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
551 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
552 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
553 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
554 sink.para()->text(L"Examples:");
555 sink.example(L">> SWAP 1 2");
556 sink.example(L">> SWAP 1-1 2-3");
557 sink.example(L">> SWAP 1-1 1-2");
558 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
561 std::wstring swap_command(command_context& ctx)
563 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
565 if (ctx.layer_index(-1) != -1)
567 std::vector<std::string> strs;
568 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
570 auto ch1 = ctx.channel.channel;
571 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
573 int l1 = ctx.layer_index();
574 int l2 = boost::lexical_cast<int>(strs.at(1));
576 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
580 auto ch1 = ctx.channel.channel;
581 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
582 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
585 return L"202 SWAP OK\r\n";
588 void add_describer(core::help_sink& sink, const core::help_repository& repo)
590 sink.short_description(L"Add a consumer to a video channel.");
591 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
593 ->text(L"Adds a consumer to the specified video channel. The string ")
594 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
595 ->text(L"If a successful match is found a consumer will be created and added to the ")
596 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
597 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
598 ->see(L"the CasparCG config file")->text(L".");
599 sink.para()->text(L"Examples:");
600 sink.example(L">> ADD 1 DECKLINK 1");
601 sink.example(L">> ADD 1 BLUEFISH 2");
602 sink.example(L">> ADD 1 SCREEN");
603 sink.example(L">> ADD 1 AUDIO");
604 sink.example(L">> ADD 1 IMAGE filename");
605 sink.example(L">> ADD 1 FILE filename.mov");
606 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
607 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
608 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
611 std::wstring add_command(command_context& ctx)
613 replace_placeholders(
614 L"<CLIENT_IP_ADDRESS>",
615 ctx.client->address(),
618 core::diagnostics::scoped_call_context save;
619 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
621 auto consumer = create_consumer(ctx.parameters, &ctx.channel.channel->stage());
622 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
624 return L"202 ADD OK\r\n";
627 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
629 sink.short_description(L"Remove a consumer from a video channel.");
630 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
632 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
633 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
634 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
635 sink.para()->text(L"Examples:");
636 sink.example(L">> REMOVE 1 DECKLINK 1");
637 sink.example(L">> REMOVE 1 BLUEFISH 2");
638 sink.example(L">> REMOVE 1 SCREEN");
639 sink.example(L">> REMOVE 1 AUDIO");
640 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
643 std::wstring remove_command(command_context& ctx)
645 auto index = ctx.layer_index(std::numeric_limits<int>::min());
647 if (index == std::numeric_limits<int>::min())
649 replace_placeholders(
650 L"<CLIENT_IP_ADDRESS>",
651 ctx.client->address(),
654 index = create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
657 ctx.channel.channel->output().remove(index);
659 return L"202 REMOVE OK\r\n";
662 void print_describer(core::help_sink& sink, const core::help_repository& repo)
664 sink.short_description(L"Take a snapshot of a channel.");
665 sink.syntax(L"PRINT [video_channel:int]");
667 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
668 ->code(L"media")->text(L" folder.");
669 sink.para()->text(L"Examples:");
670 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
673 std::wstring print_command(command_context& ctx)
675 ctx.channel.channel->output().add(create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
677 return L"202 PRINT OK\r\n";
680 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
682 sink.short_description(L"Change the log level of the server.");
683 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
684 sink.para()->text(L"Changes the log level of the server.");
685 sink.para()->text(L"Examples:");
686 sink.example(L">> LOG LEVEL trace");
687 sink.example(L">> LOG LEVEL info");
690 std::wstring log_level_command(command_context& ctx)
692 log::set_log_level(ctx.parameters.at(0));
694 return L"202 LOG OK\r\n";
697 void set_describer(core::help_sink& sink, const core::help_repository& repo)
699 sink.short_description(L"Change the value of a channel variable.");
700 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
701 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
703 ->item(L"MODE", L"Changes the video format of the channel.");
704 sink.para()->text(L"Examples:");
705 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL");
708 std::wstring set_command(command_context& ctx)
710 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
711 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
715 auto format_desc = core::video_format_desc(value);
716 if (format_desc.format != core::video_format::invalid)
718 ctx.channel.channel->video_format_desc(format_desc);
719 return L"202 SET MODE OK\r\n";
722 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
725 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
728 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
730 sink.short_description(L"Store a dataset.");
731 sink.syntax(L"DATA STORE [name:string] [data:string]");
732 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
733 sink.para()->text(L"Directories will be created if they do not exist.");
734 sink.para()->text(L"Examples:");
735 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
736 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
739 std::wstring data_store_command(command_context& ctx)
741 std::wstring filename = env::data_folder();
742 filename.append(ctx.parameters[0]);
743 filename.append(L".ftd");
745 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
746 auto found_data_path = find_case_insensitive(data_path);
749 data_path = *found_data_path;
751 if (!boost::filesystem::exists(data_path))
752 boost::filesystem::create_directories(data_path);
754 auto found_filename = find_case_insensitive(filename);
757 filename = *found_filename; // Overwrite case insensitive.
759 boost::filesystem::wofstream datafile(filename);
761 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
763 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
764 datafile << ctx.parameters[1] << std::flush;
767 return L"202 DATA STORE OK\r\n";
770 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
772 sink.short_description(L"Retrieve a dataset.");
773 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
774 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
775 sink.para()->text(L"Examples:");
776 sink.example(L">> DATA RETRIEVE my_data");
777 sink.example(L">> DATA RETRIEVE Folder1/my_data");
780 std::wstring data_retrieve_command(command_context& ctx)
782 std::wstring filename = env::data_folder();
783 filename.append(ctx.parameters[0]);
784 filename.append(L".ftd");
786 std::wstring file_contents;
788 auto found_file = find_case_insensitive(filename);
791 file_contents = read_file(boost::filesystem::path(*found_file));
793 if (file_contents.empty())
794 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
796 std::wstringstream reply;
797 reply << L"201 DATA RETRIEVE OK\r\n";
799 std::wstringstream file_contents_stream(file_contents);
802 bool firstLine = true;
803 while (std::getline(file_contents_stream, line))
817 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
819 sink.short_description(L"List stored datasets.");
820 sink.syntax(L"DATA LIST");
821 sink.para()->text(L"Returns a list of all stored datasets.");
824 std::wstring data_list_command(command_context& ctx)
826 std::wstringstream replyString;
827 replyString << L"200 DATA LIST OK\r\n";
829 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
831 if (boost::filesystem::is_regular_file(itr->path()))
833 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
836 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
838 auto str = relativePath.replace_extension(L"").generic_wstring();
839 if (str[0] == L'\\' || str[0] == L'/')
840 str = std::wstring(str.begin() + 1, str.end());
842 replyString << str << L"\r\n";
846 replyString << L"\r\n";
848 return boost::to_upper_copy(replyString.str());
851 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
853 sink.short_description(L"Remove a stored dataset.");
854 sink.syntax(L"DATA REMOVE [name:string]");
855 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
856 sink.para()->text(L"Examples:");
857 sink.example(L">> DATA REMOVE my_data");
858 sink.example(L">> DATA REMOVE Folder1/my_data");
861 std::wstring data_remove_command(command_context& ctx)
863 std::wstring filename = env::data_folder();
864 filename.append(ctx.parameters[0]);
865 filename.append(L".ftd");
867 if (!boost::filesystem::exists(filename))
868 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
870 if (!boost::filesystem::remove(filename))
871 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
873 return L"201 DATA REMOVE OK\r\n";
876 // Template Graphics Commands
878 int get_and_validate_layer(const std::wstring& layerstring) {
879 int length = layerstring.length();
880 for (int i = 0; i < length; ++i) {
881 if (!std::isdigit(layerstring[i])) {
882 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
886 return boost::lexical_cast<int>(layerstring);
889 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
891 sink.short_description(L"Prepare a template for displaying.");
892 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
894 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
895 ->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.");
896 sink.para()->text(L"Examples:");
897 sink.example(L"CG 1 ADD 10 svtnews/info 1");
900 std::wstring cg_add_command(command_context& ctx)
902 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
904 int layer = get_and_validate_layer(ctx.parameters.at(0));
905 std::wstring label; //_parameters[2]
906 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
907 unsigned int dataIndex = 3;
909 if (ctx.parameters.at(2).length() > 1)
911 label = ctx.parameters.at(2);
914 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
915 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
918 { //read play-on-load-flag
919 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
922 const wchar_t* pDataString = 0;
923 std::wstring dataFromFile;
924 if (ctx.parameters.size() > dataIndex)
926 const std::wstring& dataString = ctx.parameters.at(dataIndex);
928 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
929 pDataString = dataString.c_str();
932 //The data is not an XML-string, it must be a filename
933 std::wstring filename = env::data_folder();
934 filename.append(dataString);
935 filename.append(L".ftd");
937 auto found_file = find_case_insensitive(filename);
941 dataFromFile = read_file(boost::filesystem::path(*found_file));
942 pDataString = dataFromFile.c_str();
947 auto filename = ctx.parameters.at(1);
948 auto proxy = ctx.cg_registry->get_or_create_proxy(
949 spl::make_shared_ptr(ctx.channel.channel),
950 get_producer_dependencies(ctx.channel.channel, ctx),
951 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
954 if (proxy == core::cg_proxy::empty())
955 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
957 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
959 return L"202 CG OK\r\n";
962 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
964 sink.short_description(L"Play and display a template.");
965 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
966 sink.para()->text(L"Plays and displays the template in the specified layer.");
967 sink.para()->text(L"Examples:");
968 sink.example(L"CG 1 PLAY 0");
971 std::wstring cg_play_command(command_context& ctx)
973 int layer = get_and_validate_layer(ctx.parameters.at(0));
974 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
976 return L"202 CG OK\r\n";
979 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
981 sink.short_description(L"Stop and remove a template.");
982 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
984 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
985 ->text(L" in that the template gets a chance to animate out when it is stopped.");
986 sink.para()->text(L"Examples:");
987 sink.example(L"CG 1 STOP 0");
990 std::wstring cg_stop_command(command_context& ctx)
992 int layer = get_and_validate_layer(ctx.parameters.at(0));
993 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->stop(layer, 0);
995 return L"202 CG OK\r\n";
998 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1000 sink.short_description(LR"(Trigger a "continue" in a template.)");
1001 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1003 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1004 ->text(L"This is used to control animations that has multiple discreet steps.");
1005 sink.para()->text(L"Examples:");
1006 sink.example(L"CG 1 NEXT 0");
1009 std::wstring cg_next_command(command_context& ctx)
1011 int layer = get_and_validate_layer(ctx.parameters.at(0));
1012 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->next(layer);
1014 return L"202 CG OK\r\n";
1017 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1019 sink.short_description(L"Remove a template.");
1020 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1021 sink.para()->text(L"Removes the template from the specified layer.");
1022 sink.para()->text(L"Examples:");
1023 sink.example(L"CG 1 REMOVE 0");
1026 std::wstring cg_remove_command(command_context& ctx)
1028 int layer = get_and_validate_layer(ctx.parameters.at(0));
1029 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->remove(layer);
1031 return L"202 CG OK\r\n";
1034 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1036 sink.short_description(L"Remove all templates on a video layer.");
1037 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1038 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1039 sink.para()->text(L"Examples:");
1040 sink.example(L"CG 1 CLEAR");
1043 std::wstring cg_clear_command(command_context& ctx)
1045 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1047 return L"202 CG OK\r\n";
1050 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1052 sink.short_description(L"Update a template with new data.");
1053 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1054 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.");
1057 std::wstring cg_update_command(command_context& ctx)
1059 int layer = get_and_validate_layer(ctx.parameters.at(0));
1061 std::wstring dataString = ctx.parameters.at(1);
1062 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1064 //The data is not XML or Json, it must be a filename
1065 std::wstring filename = env::data_folder();
1066 filename.append(dataString);
1067 filename.append(L".ftd");
1069 dataString = read_file(boost::filesystem::path(filename));
1072 ctx.cg_registry->get_proxy(
1073 spl::make_shared_ptr(ctx.channel.channel),
1074 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1075 ->update(layer, dataString);
1077 return L"202 CG OK\r\n";
1080 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1082 sink.short_description(L"Invoke a method/label on a template.");
1083 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1084 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1085 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1088 std::wstring cg_invoke_command(command_context& ctx)
1090 std::wstringstream replyString;
1091 replyString << L"201 CG OK\r\n";
1092 int layer = get_and_validate_layer(ctx.parameters.at(0));
1093 auto result = ctx.cg_registry->get_proxy(
1094 spl::make_shared_ptr(ctx.channel.channel),
1095 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1096 ->invoke(layer, ctx.parameters.at(1));
1097 replyString << result << L"\r\n";
1099 return replyString.str();
1102 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1104 sink.short_description(L"Get information about a running template or the template host.");
1105 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1106 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1107 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1110 std::wstring cg_info_command(command_context& ctx)
1112 std::wstringstream replyString;
1113 replyString << L"201 CG OK\r\n";
1115 if (ctx.parameters.empty())
1117 auto info = ctx.cg_registry->get_proxy(
1118 spl::make_shared_ptr(ctx.channel.channel),
1119 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1120 ->template_host_info();
1121 replyString << info << L"\r\n";
1125 int layer = get_and_validate_layer(ctx.parameters.at(0));
1126 auto desc = ctx.cg_registry->get_proxy(
1127 spl::make_shared_ptr(ctx.channel.channel),
1128 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1129 ->description(layer);
1131 replyString << desc << L"\r\n";
1134 return replyString.str();
1139 core::frame_transform get_current_transform(command_context& ctx)
1141 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1144 template<typename Func>
1145 std::wstring reply_value(command_context& ctx, const Func& extractor)
1147 auto value = extractor(get_current_transform(ctx));
1149 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1152 class transforms_applier
1154 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1156 std::vector<stage::transform_tuple_t> transforms_;
1157 command_context& ctx_;
1160 transforms_applier(command_context& ctx)
1163 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1166 ctx.parameters.pop_back();
1169 void add(stage::transform_tuple_t&& transform)
1171 transforms_.push_back(std::move(transform));
1174 void commit_deferred()
1176 ctx_.channel.channel->stage().apply_transforms(
1177 std::move(deferred_transforms_[ctx_.channel_index]));
1184 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1185 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1188 ctx_.channel.channel->stage().apply_transforms(transforms_);
1191 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1193 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1195 sink.short_description(L"Let a layer act as alpha for the one obove.");
1196 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1198 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1199 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1200 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1201 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1202 ->text(L"instead it will be used as the key for the layer above.");
1203 sink.para()->text(L"Examples:");
1204 sink.example(L">> MIXER 1-0 KEYER 1");
1206 L">> MIXER 1-0 KEYER\n"
1207 L"<< 201 MIXER OK\n"
1208 L"<< 1", L"to retrieve the current state");
1211 std::wstring mixer_keyer_command(command_context& ctx)
1213 if (ctx.parameters.empty())
1214 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1216 transforms_applier transforms(ctx);
1217 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1218 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1220 transform.image_transform.is_key = value;
1225 return L"202 MIXER OK\r\n";
1228 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1230 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1232 sink.short_description(L"Enable chroma keying on a layer.");
1233 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1235 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1236 sink.para()->text(L"Examples:");
1237 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1238 sink.example(L">> MIXER 1-1 CHROMA none");
1240 L">> MIXER 1-1 BLEND\n"
1241 L"<< 201 MIXER OK\n"
1242 L"<< SCREEN", L"for getting the current blend mode");
1245 std::wstring mixer_chroma_command(command_context& ctx)
1247 if (ctx.parameters.empty())
1249 auto chroma = get_current_transform(ctx).image_transform.chroma;
1250 return L"201 MIXER OK\r\n"
1251 + core::get_chroma_mode(chroma.key) + L" "
1252 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1253 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1254 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1257 transforms_applier transforms(ctx);
1258 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1259 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1261 core::chroma chroma;
1262 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1264 if (chroma.key != core::chroma::type::none)
1266 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1267 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1268 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1271 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1273 transform.image_transform.chroma = chroma;
1275 }, duration, tween));
1278 return L"202 MIXER OK\r\n";
1281 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1283 sink.short_description(L"Set the blend mode for a layer.");
1284 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1286 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1287 ->text(L"If no argument is given the current blend mode is returned.");
1289 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1290 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1291 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1292 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1293 sink.para()->text(L"Examples:");
1294 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1296 L">> MIXER 1-1 BLEND\n"
1297 L"<< 201 MIXER OK\n"
1298 L"<< SCREEN", L"for getting the current blend mode");
1299 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1302 std::wstring mixer_blend_command(command_context& ctx)
1304 if (ctx.parameters.empty())
1305 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1307 transforms_applier transforms(ctx);
1308 auto value = get_blend_mode(ctx.parameters.at(0));
1309 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1311 transform.image_transform.blend_mode = value;
1316 return L"202 MIXER OK\r\n";
1319 template<typename Getter, typename Setter>
1320 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1322 if (ctx.parameters.empty())
1323 return reply_value(ctx, getter);
1325 transforms_applier transforms(ctx);
1326 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1327 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1328 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1330 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1332 setter(transform, value);
1334 }, duration, tween));
1337 return L"202 MIXER OK\r\n";
1340 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1342 sink.short_description(L"Change the opacity of a layer.");
1343 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1344 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1345 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1346 sink.para()->text(L"Examples:");
1347 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1349 L">> MIXER 1-0 OPACITY\n"
1350 L"<< 201 MIXER OK\n"
1351 L"<< 0.5", L"to retrieve the current opacity");
1354 std::wstring mixer_opacity_command(command_context& ctx)
1356 return single_double_animatable_mixer_command(
1358 [](const frame_transform& t) { return t.image_transform.opacity; },
1359 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1362 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1364 sink.short_description(L"Change the brightness of a layer.");
1365 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1366 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1367 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1368 sink.para()->text(L"Examples:");
1369 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1371 L">> MIXER 1-0 BRIGHTNESS\n"
1372 L"<< 201 MIXER OK\n"
1373 L"0.5", L"to retrieve the current brightness");
1376 std::wstring mixer_brightness_command(command_context& ctx)
1378 return single_double_animatable_mixer_command(
1380 [](const frame_transform& t) { return t.image_transform.brightness; },
1381 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1384 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1386 sink.short_description(L"Change the saturation of a layer.");
1387 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1388 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1389 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1390 sink.para()->text(L"Examples:");
1391 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1393 L">> MIXER 1-0 SATURATION\n"
1394 L"<< 201 MIXER OK\n"
1395 L"<< 0.5", L"to retrieve the current saturation");
1398 std::wstring mixer_saturation_command(command_context& ctx)
1400 return single_double_animatable_mixer_command(
1402 [](const frame_transform& t) { return t.image_transform.saturation; },
1403 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1406 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1408 sink.short_description(L"Change the contrast of a layer.");
1409 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1410 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1411 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1412 sink.para()->text(L"Examples:");
1413 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1415 L">> MIXER 1-0 CONTRAST\n"
1416 L"<< 201 MIXER OK\n"
1417 L"<< 0.5", L"to retrieve the current contrast");
1420 std::wstring mixer_contrast_command(command_context& ctx)
1422 return single_double_animatable_mixer_command(
1424 [](const frame_transform& t) { return t.image_transform.contrast; },
1425 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1428 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1430 sink.short_description(L"Adjust the video levels of a layer.");
1431 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);
1433 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1435 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1436 ->item(L"gamma", L"Adjusts the gamma of the image.")
1437 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1438 sink.para()->text(L"Examples:");
1439 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");
1440 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");
1441 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1443 L">> MIXER 1-10 LEVELS\n"
1444 L"<< 201 MIXER OK\n"
1445 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1448 std::wstring mixer_levels_command(command_context& ctx)
1450 if (ctx.parameters.empty())
1452 auto levels = get_current_transform(ctx).image_transform.levels;
1453 return L"201 MIXER OK\r\n"
1454 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1455 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1456 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1457 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1458 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1461 transforms_applier transforms(ctx);
1463 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1464 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1465 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1466 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1467 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1468 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1469 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1471 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1473 transform.image_transform.levels = value;
1475 }, duration, tween));
1478 return L"202 MIXER OK\r\n";
1481 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1483 sink.short_description(L"Change the fill position and scale of a layer.");
1484 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1486 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1487 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1488 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1489 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1491 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1492 ->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.");
1493 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1495 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1496 ->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, ")
1497 ->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");
1499 ->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.")
1500 ->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.")
1501 ->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.")
1502 ->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.");
1503 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1504 sink.para()->text(L"Examples:");
1505 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1507 L">> MIXER 1-0 FILL\n"
1508 L"<< 201 MIXER OK\n"
1509 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1512 std::wstring mixer_fill_command(command_context& ctx)
1514 if (ctx.parameters.empty())
1516 auto transform = get_current_transform(ctx).image_transform;
1517 auto translation = transform.fill_translation;
1518 auto scale = transform.fill_scale;
1519 return L"201 MIXER OK\r\n"
1520 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1521 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1522 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1523 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1526 transforms_applier transforms(ctx);
1527 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1528 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1529 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1530 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1531 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1532 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1534 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1536 transform.image_transform.fill_translation[0] = x;
1537 transform.image_transform.fill_translation[1] = y;
1538 transform.image_transform.fill_scale[0] = x_s;
1539 transform.image_transform.fill_scale[1] = y_s;
1541 }, duration, tween));
1544 return L"202 MIXER OK\r\n";
1547 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1549 sink.short_description(L"Change the clipping viewport of a layer.");
1550 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1552 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1553 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1554 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1556 ->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.")
1557 ->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.")
1558 ->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.")
1559 ->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.");
1560 sink.para()->text(L"Examples:");
1561 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1563 L">> MIXER 1-0 CLIP\n"
1564 L"<< 201 MIXER OK\n"
1565 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1568 std::wstring mixer_clip_command(command_context& ctx)
1570 if (ctx.parameters.empty())
1572 auto transform = get_current_transform(ctx).image_transform;
1573 auto translation = transform.clip_translation;
1574 auto scale = transform.clip_scale;
1576 return L"201 MIXER OK\r\n"
1577 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1578 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1579 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1580 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1583 transforms_applier transforms(ctx);
1584 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1585 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1586 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1587 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1588 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1589 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1591 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1593 transform.image_transform.clip_translation[0] = x;
1594 transform.image_transform.clip_translation[1] = y;
1595 transform.image_transform.clip_scale[0] = x_s;
1596 transform.image_transform.clip_scale[1] = y_s;
1598 }, duration, tween));
1601 return L"202 MIXER OK\r\n";
1604 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1606 sink.short_description(L"Change the anchor point of a layer.");
1607 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1608 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1610 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1611 ->text(L" will be done from.");
1613 ->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.")
1614 ->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.");
1615 sink.para()->text(L"Examples:");
1616 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1618 L">> MIXER 1-10 ANCHOR\n"
1619 L"<< 201 MIXER OK\n"
1620 L"<< 0.5 0.6", L"gets the anchor point");
1623 std::wstring mixer_anchor_command(command_context& ctx)
1625 if (ctx.parameters.empty())
1627 auto transform = get_current_transform(ctx).image_transform;
1628 auto anchor = transform.anchor;
1629 return L"201 MIXER OK\r\n"
1630 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1631 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1634 transforms_applier transforms(ctx);
1635 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1636 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1637 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1638 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1640 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1642 transform.image_transform.anchor[0] = x;
1643 transform.image_transform.anchor[1] = y;
1645 }, duration, tween));
1648 return L"202 MIXER OK\r\n";
1651 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1653 sink.short_description(L"Crop a layer.");
1654 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);
1656 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1657 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1658 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1660 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1661 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1662 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1663 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1664 sink.para()->text(L"Examples:");
1665 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");
1667 L">> MIXER 1-0 CROP\n"
1668 L"<< 201 MIXER OK\n"
1669 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1672 std::wstring mixer_crop_command(command_context& ctx)
1674 if (ctx.parameters.empty())
1676 auto crop = get_current_transform(ctx).image_transform.crop;
1677 return L"201 MIXER OK\r\n"
1678 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1679 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1680 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1681 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1684 transforms_applier transforms(ctx);
1685 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1686 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1687 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1688 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1689 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1690 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1692 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1694 transform.image_transform.crop.ul[0] = ul_x;
1695 transform.image_transform.crop.ul[1] = ul_y;
1696 transform.image_transform.crop.lr[0] = lr_x;
1697 transform.image_transform.crop.lr[1] = lr_y;
1699 }, duration, tween));
1702 return L"202 MIXER OK\r\n";
1705 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1707 sink.short_description(L"Rotate a layer.");
1708 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1710 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1711 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1712 sink.para()->text(L"Examples:");
1713 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1715 L">> MIXER 1-0 ROTATION\n"
1716 L"<< 201 MIXER OK\n"
1717 L"<< 45", L"to retrieve the current angle");
1720 std::wstring mixer_rotation_command(command_context& ctx)
1722 static const double PI = 3.141592653589793;
1724 return single_double_animatable_mixer_command(
1726 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1727 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1730 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1732 sink.short_description(L"Adjust the perspective transform of a layer.");
1733 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);
1735 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1737 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1738 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1739 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1740 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1741 sink.para()->text(L"Examples:");
1742 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1744 L">> MIXER 1-10 PERSPECTIVE\n"
1745 L"<< 201 MIXER OK\n"
1746 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1749 std::wstring mixer_perspective_command(command_context& ctx)
1751 if (ctx.parameters.empty())
1753 auto perspective = get_current_transform(ctx).image_transform.perspective;
1756 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1757 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1758 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1759 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1760 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1761 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1762 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1763 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1766 transforms_applier transforms(ctx);
1767 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1768 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1769 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1770 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1771 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1772 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1773 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1774 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1775 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1776 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1778 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1780 transform.image_transform.perspective.ul[0] = ul_x;
1781 transform.image_transform.perspective.ul[1] = ul_y;
1782 transform.image_transform.perspective.ur[0] = ur_x;
1783 transform.image_transform.perspective.ur[1] = ur_y;
1784 transform.image_transform.perspective.lr[0] = lr_x;
1785 transform.image_transform.perspective.lr[1] = lr_y;
1786 transform.image_transform.perspective.ll[0] = ll_x;
1787 transform.image_transform.perspective.ll[1] = ll_y;
1789 }, duration, tween));
1792 return L"202 MIXER OK\r\n";
1795 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1797 sink.short_description(L"Enable or disable mipmapping for a layer.");
1798 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1800 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1801 ->text(L"If no argument is given the current state is returned.");
1802 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1803 sink.para()->text(L"Examples:");
1804 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turing mipmapping on");
1806 L">> MIXER 1-10 MIPMAP\n"
1807 L"<< 201 MIXER OK\n"
1808 L"<< 1", L"for getting the current state");
1811 std::wstring mixer_mipmap_command(command_context& ctx)
1813 if (ctx.parameters.empty())
1814 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1816 transforms_applier transforms(ctx);
1817 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1818 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1820 transform.image_transform.use_mipmap = value;
1825 return L"202 MIXER OK\r\n";
1828 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1830 sink.short_description(L"Change the volume of a layer.");
1831 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1832 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1833 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1834 sink.para()->text(L"Examples:");
1835 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1836 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1838 L">> MIXER 1-0 VOLUME\n"
1839 L"<< 201 MIXER OK\n"
1840 L"<< 0.8", L"to retrieve the current volume");
1843 std::wstring mixer_volume_command(command_context& ctx)
1845 return single_double_animatable_mixer_command(
1847 [](const frame_transform& t) { return t.audio_transform.volume; },
1848 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1851 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1853 sink.short_description(L"Change the volume of an entire channel.");
1854 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1855 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1856 sink.para()->text(L"Examples:");
1857 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1858 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1859 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1862 std::wstring mixer_mastervolume_command(command_context& ctx)
1864 if (ctx.parameters.empty())
1866 auto volume = ctx.channel.channel->mixer().get_master_volume();
1867 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1870 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1871 ctx.channel.channel->mixer().set_master_volume(master_volume);
1873 return L"202 MIXER OK\r\n";
1876 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1878 sink.short_description(L"Create a grid of video layers.");
1879 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1881 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1882 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1883 sink.para()->text(L"Examples:");
1884 sink.example(L">> MIXER 1 GRID 2");
1887 std::wstring mixer_grid_command(command_context& ctx)
1889 transforms_applier transforms(ctx);
1890 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1891 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1892 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1893 double delta = 1.0 / static_cast<double>(n);
1894 for (int x = 0; x < n; ++x)
1896 for (int y = 0; y < n; ++y)
1898 int index = x + y*n + 1;
1899 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1901 transform.image_transform.fill_translation[0] = x*delta;
1902 transform.image_transform.fill_translation[1] = y*delta;
1903 transform.image_transform.fill_scale[0] = delta;
1904 transform.image_transform.fill_scale[1] = delta;
1905 transform.image_transform.clip_translation[0] = x*delta;
1906 transform.image_transform.clip_translation[1] = y*delta;
1907 transform.image_transform.clip_scale[0] = delta;
1908 transform.image_transform.clip_scale[1] = delta;
1910 }, duration, tween));
1915 return L"202 MIXER OK\r\n";
1918 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1920 sink.short_description(L"Commit all deferred mixer transforms.");
1921 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} COMMIT");
1922 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1923 sink.para()->text(L"Examples:");
1925 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1926 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1927 L">> MIXER 1 COMMIT");
1930 std::wstring mixer_commit_command(command_context& ctx)
1932 transforms_applier transforms(ctx);
1933 transforms.commit_deferred();
1935 return L"202 MIXER OK\r\n";
1938 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1940 sink.short_description(L"Clear all transformations on a channel or layer.");
1941 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1942 sink.para()->text(L"Clears all transformations on a channel or layer.");
1943 sink.para()->text(L"Examples:");
1944 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1945 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1948 std::wstring mixer_clear_command(command_context& ctx)
1950 int layer = ctx.layer_id;
1953 ctx.channel.channel->stage().clear_transforms();
1955 ctx.channel.channel->stage().clear_transforms(layer);
1957 return L"202 MIXER OK\r\n";
1960 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1962 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
1963 sink.syntax(L"CHANNEL_GRID");
1964 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
1966 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
1967 ->code(L"casparcg.config")->text(L" for this to work correctly.");
1970 std::wstring channel_grid_command(command_context& ctx)
1973 auto self = ctx.channels.back();
1975 core::diagnostics::scoped_call_context save;
1976 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
1978 std::vector<std::wstring> params;
1979 params.push_back(L"SCREEN");
1980 params.push_back(L"0");
1981 params.push_back(L"NAME");
1982 params.push_back(L"Channel Grid Window");
1983 auto screen = create_consumer(params, &self.channel->stage());
1985 self.channel->output().add(screen);
1987 for (auto& channel : ctx.channels)
1989 if (channel.channel != self.channel)
1991 core::diagnostics::call_context::for_thread().layer = index;
1992 auto producer = create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
1993 self.channel->stage().load(index, producer, false);
1994 self.channel->stage().play(index);
1999 auto num_channels = ctx.channels.size() - 1;
2000 int square_side_length = std::ceil(std::sqrt(num_channels));
2002 ctx.channel_index = self.channel->index();
2004 ctx.parameters.clear();
2005 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2006 mixer_grid_command(ctx);
2008 return L"202 CHANNEL_GRID OK\r\n";
2011 // Thumbnail Commands
2013 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2015 sink.short_description(L"List all thumbnails.");
2016 sink.syntax(L"THUMBNAIL LIST");
2017 sink.para()->text(L"Lists all thumbnails.");
2018 sink.para()->text(L"Examples:");
2020 L">> THUMBNAIL LIST\n"
2021 L"<< 200 THUMBNAIL LIST OK\n"
2022 L"<< \"AMB\" 20130301T124409 1149\n"
2023 L"<< \"foo/bar\" 20130523T234001 244");
2026 std::wstring thumbnail_list_command(command_context& ctx)
2028 std::wstringstream replyString;
2029 replyString << L"200 THUMBNAIL LIST OK\r\n";
2031 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2033 if (boost::filesystem::is_regular_file(itr->path()))
2035 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2038 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
2040 auto str = relativePath.replace_extension(L"").generic_wstring();
2041 if (str[0] == '\\' || str[0] == '/')
2042 str = std::wstring(str.begin() + 1, str.end());
2044 auto mtime = boost::filesystem::last_write_time(itr->path());
2045 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2046 auto file_size = boost::filesystem::file_size(itr->path());
2048 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2052 replyString << L"\r\n";
2054 return boost::to_upper_copy(replyString.str());
2057 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2059 sink.short_description(L"Retrieve a thumbnail.");
2060 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2061 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2062 sink.para()->text(L"Examples:");
2064 L">> THUMBNAIL RETRIEVE foo/bar\n"
2065 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2066 L"<< ...base64 data...");
2069 std::wstring thumbnail_retrieve_command(command_context& ctx)
2071 std::wstring filename = env::thumbnails_folder();
2072 filename.append(ctx.parameters.at(0));
2073 filename.append(L".png");
2075 std::wstring file_contents;
2077 auto found_file = find_case_insensitive(filename);
2080 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2082 if (file_contents.empty())
2083 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2085 std::wstringstream reply;
2087 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2088 reply << file_contents;
2093 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2095 sink.short_description(L"Regenerate a thumbnail.");
2096 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2097 sink.para()->text(L"Regenerates a thumbnail.");
2100 std::wstring thumbnail_generate_command(command_context& ctx)
2104 ctx.thumb_gen->generate(ctx.parameters.at(0));
2105 return L"202 THUMBNAIL GENERATE OK\r\n";
2108 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2111 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2113 sink.short_description(L"Regenerate all thumbnails.");
2114 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2115 sink.para()->text(L"Regenerates all thumbnails.");
2118 std::wstring thumbnail_generateall_command(command_context& ctx)
2122 ctx.thumb_gen->generate_all();
2123 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2126 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2131 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2133 sink.short_description(L"Get information about a media file.");
2134 sink.syntax(L"CINF [filename:string]");
2135 sink.para()->text(L"Returns information about a media file.");
2138 std::wstring cinf_command(command_context& ctx)
2141 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2143 auto path = itr->path();
2144 auto file = path.replace_extension(L"").filename().wstring();
2145 if (boost::iequals(file, ctx.parameters.at(0)))
2146 info += MediaInfo(itr->path(), ctx.media_info_repo) + L"\r\n";
2150 CASPAR_THROW_EXCEPTION(file_not_found());
2152 std::wstringstream replyString;
2153 replyString << L"200 CINF OK\r\n";
2154 replyString << info << "\r\n";
2156 return replyString.str();
2159 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2161 sink.short_description(L"List all media files.");
2162 sink.syntax(L"CLS");
2164 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2165 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2168 std::wstring cls_command(command_context& ctx)
2170 std::wstringstream replyString;
2171 replyString << L"200 CLS OK\r\n";
2172 replyString << ListMedia(ctx.media_info_repo);
2173 replyString << L"\r\n";
2174 return boost::to_upper_copy(replyString.str());
2177 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2179 sink.short_description(L"List all templates.");
2180 sink.syntax(L"TLS");
2182 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2183 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2186 std::wstring tls_command(command_context& ctx)
2188 std::wstringstream replyString;
2189 replyString << L"200 TLS OK\r\n";
2191 replyString << ListTemplates(ctx.cg_registry);
2192 replyString << L"\r\n";
2194 return replyString.str();
2197 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2199 sink.short_description(L"Get version information.");
2200 sink.syntax(L"VERSION {[component:string]}");
2201 sink.para()->text(L"Returns the version of specified component.");
2202 sink.para()->text(L"Examples:");
2205 L"<< 201 VERSION OK\n"
2206 L"<< 2.1.0.f207a33 STABLE");
2208 L">> VERSION SERVER\n"
2209 L"<< 201 VERSION OK\n"
2210 L"<< 2.1.0.f207a33 STABLE");
2212 L">> VERSION FLASH\n"
2213 L"<< 201 VERSION OK\n"
2217 std::wstring version_command(command_context& ctx)
2219 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2221 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2223 return L"201 VERSION OK\r\n" + version + L"\r\n";
2226 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2229 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2231 sink.short_description(L"Get a list of the available channels.");
2232 sink.syntax(L"INFO");
2233 sink.para()->text(L"Retrieves a list of the available channels.");
2237 L"<< 1 720p5000 PLAYING\n"
2238 L"<< 2 PAL PLAYING");
2241 std::wstring info_command(command_context& ctx)
2243 std::wstringstream replyString;
2244 // This is needed for backwards compatibility with old clients
2245 replyString << L"200 INFO OK\r\n";
2246 for (size_t n = 0; n < ctx.channels.size(); ++n)
2247 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2248 replyString << L"\r\n";
2249 return replyString.str();
2252 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2254 std::wstringstream replyString;
2256 if (command.empty())
2257 replyString << L"201 INFO OK\r\n";
2259 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2261 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2262 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2263 replyString << L"\r\n";
2264 return replyString.str();
2267 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2269 sink.short_description(L"Get information about a channel or a layer.");
2270 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2271 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2272 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2275 std::wstring info_channel_command(command_context& ctx)
2277 boost::property_tree::wptree info;
2278 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2280 if (layer == std::numeric_limits<int>::min())
2282 info.add_child(L"channel", ctx.channel.channel->info())
2283 .add(L"index", ctx.channel_index);
2287 if (ctx.parameters.size() >= 1)
2289 if (boost::iequals(ctx.parameters.at(0), L"B"))
2290 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2292 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2296 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2300 return create_info_xml_reply(info);
2303 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2305 sink.short_description(L"Get information about a template.");
2306 sink.syntax(L"INFO TEMPLATE [template:string]");
2307 sink.para()->text(L"Gets information about the specified template.");
2310 std::wstring info_template_command(command_context& ctx)
2312 auto filename = ctx.parameters.at(0);
2314 std::wstringstream str;
2315 str << u16(ctx.cg_registry->read_meta_info(filename));
2316 boost::property_tree::wptree info;
2317 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2319 return create_info_xml_reply(info, L"TEMPLATE");
2322 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2324 sink.short_description(L"Get the contents of the configuration used.");
2325 sink.syntax(L"INFO CONFIG");
2326 sink.para()->text(L"Gets the contents of the configuration used.");
2329 std::wstring info_config_command(command_context& ctx)
2331 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2334 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2336 sink.short_description(L"Get information about the paths used.");
2337 sink.syntax(L"INFO PATHS");
2338 sink.para()->text(L"Gets information about the paths used.");
2341 std::wstring info_paths_command(command_context& ctx)
2343 boost::property_tree::wptree info;
2344 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2345 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2347 return create_info_xml_reply(info, L"PATHS");
2350 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2352 sink.short_description(L"Get system information.");
2353 sink.syntax(L"INFO SYSTEM");
2354 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2357 std::wstring info_system_command(command_context& ctx)
2359 boost::property_tree::wptree info;
2361 info.add(L"system.name", caspar::system_product_name());
2362 info.add(L"system.os.description", caspar::os_description());
2363 info.add(L"system.cpu", caspar::cpu_info());
2365 ctx.system_info_repo->fill_information(info);
2367 return create_info_xml_reply(info, L"SYSTEM");
2370 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2372 sink.short_description(L"Get detailed information about all channels.");
2373 sink.syntax(L"INFO SERVER");
2374 sink.para()->text(L"Gets detailed information about all channels.");
2377 std::wstring info_server_command(command_context& ctx)
2379 boost::property_tree::wptree info;
2382 for (auto& channel : ctx.channels)
2383 info.add_child(L"channels.channel", channel.channel->info())
2384 .add(L"index", ++index);
2386 return create_info_xml_reply(info, L"SERVER");
2389 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2391 sink.short_description(L"Open the diagnostics window.");
2392 sink.syntax(L"DIAG");
2393 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2396 std::wstring diag_command(command_context& ctx)
2398 core::diagnostics::osd::show_graphs(true);
2400 return L"202 DIAG OK\r\n";
2403 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2405 sink.short_description(L"Show online help.");
2406 sink.syntax(LR"(HELP {[command:string]})");
2407 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2408 sink.example(L">> HELP", L"Shows a list of commands.");
2409 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2412 std::wstring help_command(command_context& ctx)
2414 struct max_width_sink : public core::help_sink
2416 std::size_t max_width = 0;
2418 void begin_item(const std::wstring& name) override
2420 max_width = std::max(name.length(), max_width);
2424 struct short_description_sink : public core::help_sink
2427 std::wstringstream& out;
2429 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2431 void begin_item(const std::wstring& name) override
2433 out << std::left << std::setw(width + 1) << name;
2436 void short_description(const std::wstring& short_description) override
2438 out << short_description << L"\r\n";
2442 struct simple_paragraph_builder : core::paragraph_builder
2444 std::wstringstream& out;
2446 simple_paragraph_builder(std::wstringstream& out) : out(out) { }
2447 ~simple_paragraph_builder()
2451 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2453 out << std::move(text);
2454 return shared_from_this();
2456 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2457 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2458 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2461 struct simple_definition_list_builder : core::definition_list_builder
2463 std::wstringstream& out;
2465 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2466 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2468 out << L" " << std::move(term) << L"\n";
2469 out << L" " << std::move(description) << L"\n\n";
2470 return shared_from_this();
2474 struct long_description_sink : public core::help_sink
2476 std::wstringstream& out;
2478 long_description_sink(std::wstringstream& out) : out(out) { }
2480 void syntax(const std::wstring& syntax) override
2483 out << L" " << syntax << L"\n\n";
2486 spl::shared_ptr<core::paragraph_builder> para() override
2488 return spl::make_shared<simple_paragraph_builder>(out);
2491 spl::shared_ptr<core::definition_list_builder> definitions() override
2493 return spl::make_shared<simple_definition_list_builder>(out);
2496 void example(const std::wstring& code, const std::wstring& caption = L"") override
2498 out << L" " << code << L"\n";
2499 if (!caption.empty())
2500 out << L" ..." << caption << L"\n";
2504 void begin_item(const std::wstring& name) override
2506 out << name << L"\n\n";
2510 std::wstringstream result;
2512 if (ctx.parameters.size() == 0)
2514 result << L"200 HELP OK\r\n";
2515 max_width_sink width;
2516 ctx.help_repo->help({ L"AMCP" }, width);
2517 short_description_sink sink(width.max_width, result);
2518 sink.width = width.max_width;
2519 ctx.help_repo->help({ L"AMCP" }, sink);
2524 result << L"201 HELP OK\r\n";
2525 auto command = boost::to_upper_copy(
2526 ctx.parameters.size() == 2
2527 ? ctx.parameters.at(0) + L" " + ctx.parameters.at(1)
2528 : ctx.parameters.at(0));
2529 long_description_sink sink(result);
2530 ctx.help_repo->help({ L"AMCP" }, command, sink);
2534 return result.str();
2537 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2539 sink.short_description(L"Disconnect the session.");
2540 sink.syntax(L"BYE");
2542 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2543 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2546 std::wstring bye_command(command_context& ctx)
2548 ctx.client->disconnect();
2552 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2554 sink.short_description(L"Shutdown the server.");
2555 sink.syntax(L"KILL");
2556 sink.para()->text(L"Shuts the server down.");
2559 std::wstring kill_command(command_context& ctx)
2561 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2562 return L"202 KILL OK\r\n";
2565 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2567 sink.short_description(L"Shutdown the server with restart exit code.");
2568 sink.syntax(L"RESTART");
2570 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2571 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2574 std::wstring restart_command(command_context& ctx)
2576 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2577 return L"202 RESTART OK\r\n";
2580 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2582 sink.short_description(L"Lock or unlock access to a channel.");
2583 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2584 sink.para()->text(L"Allows for exclusive access to a channel.");
2585 sink.para()->text(L"Examples:");
2586 sink.example(L"LOCK 1 ACQUIRE secret");
2587 sink.example(L"LOCK 1 RELEASE");
2588 sink.example(L"LOCK 1 CLEAR");
2591 std::wstring lock_command(command_context& ctx)
2593 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2594 auto lock = ctx.channels.at(channel_index).lock;
2595 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2597 if (command == L"ACQUIRE")
2599 std::wstring lock_phrase = ctx.parameters.at(2);
2601 //TODO: read options
2603 //just lock one channel
2604 if (!lock->try_lock(lock_phrase, ctx.client))
2605 return L"503 LOCK ACQUIRE FAILED\r\n";
2607 return L"202 LOCK ACQUIRE OK\r\n";
2609 else if (command == L"RELEASE")
2611 lock->release_lock(ctx.client);
2612 return L"202 LOCK RELEASE OK\r\n";
2614 else if (command == L"CLEAR")
2616 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2617 std::wstring client_override_phrase;
2619 if (!override_phrase.empty())
2620 client_override_phrase = ctx.parameters.at(2);
2622 //just clear one channel
2623 if (client_override_phrase != override_phrase)
2624 return L"503 LOCK CLEAR FAILED\r\n";
2626 lock->clear_locks();
2628 return L"202 LOCK CLEAR OK\r\n";
2631 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2634 /*bool LockCommand::DoExecute()
2638 auto it = parameters().begin();
2640 std::shared_ptr<caspar::IO::lock_container> lock;
2643 int channel_index = boost::lexical_cast<int>(*it) - 1;
2644 lock = channels().at(channel_index).lock;
2646 catch(const boost::bad_lexical_cast&) {}
2649 SetReplyString(L"401 LOCK ERROR\r\n");
2656 if(it == parameters().end()) //too few parameters
2658 SetReplyString(L"402 LOCK ERROR\r\n");
2662 std::wstring command = boost::to_upper_copy(*it);
2663 if(command == L"ACQUIRE")
2666 if(it == parameters().end()) //too few parameters
2668 SetReplyString(L"402 LOCK ACQUIRE ERROR\r\n");
2671 std::wstring lock_phrase = (*it);
2673 //TODO: read options
2677 //just lock one channel
2678 if(!lock->try_lock(lock_phrase, client()))
2680 SetReplyString(L"503 LOCK ACQUIRE FAILED\r\n");
2686 //TODO: lock all channels
2687 CASPAR_THROW_EXCEPTION(not_implemented());
2689 SetReplyString(L"202 LOCK ACQUIRE OK\r\n");
2692 else if(command == L"RELEASE")
2696 lock->release_lock(client());
2700 //TODO: release all channels
2701 CASPAR_THROW_EXCEPTION(not_implemented());
2703 SetReplyString(L"202 LOCK RELEASE OK\r\n");
2705 else if(command == L"CLEAR")
2707 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2708 std::wstring client_override_phrase;
2709 if(!override_phrase.empty())
2712 if(it == parameters().end())
2714 SetReplyString(L"402 LOCK CLEAR ERROR\r\n");
2717 client_override_phrase = (*it);
2722 //just clear one channel
2723 if(client_override_phrase != override_phrase)
2725 SetReplyString(L"503 LOCK CLEAR FAILED\r\n");
2729 lock->clear_locks();
2733 //TODO: clear all channels
2734 CASPAR_THROW_EXCEPTION(not_implemented());
2737 SetReplyString(L"202 LOCK CLEAR OK\r\n");
2741 SetReplyString(L"403 LOCK ERROR\r\n");
2745 catch(not_implemented&)
2747 SetReplyString(L"600 LOCK FAILED\r\n");
2752 CASPAR_LOG_CURRENT_EXCEPTION();
2753 SetReplyString(L"501 LOCK FAILED\r\n");
2760 void register_commands(amcp_command_repository& repo)
2762 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2763 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2764 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2765 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2766 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2767 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2768 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2769 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2770 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2771 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2772 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2773 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2774 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2775 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2776 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2778 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2779 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2780 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2781 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2783 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2784 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2785 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2786 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2787 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2788 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2789 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2790 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2791 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2793 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2794 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2795 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2796 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2797 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2798 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2799 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2800 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2801 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2802 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2803 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2804 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2805 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2806 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2807 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2808 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2809 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2810 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2811 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2812 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2813 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2815 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2816 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2817 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2818 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2820 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2821 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2822 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2823 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2824 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2825 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2826 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2827 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2828 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2829 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2830 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2831 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2832 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2833 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2834 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2835 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2839 }} //namespace caspar