2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Nicklas P Andersson
22 #include "../StdAfx.h"
25 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
28 #include "AMCPCommandsImpl.h"
30 #include "amcp_command_repository.h"
31 #include "AMCPCommandQueue.h"
33 #include <common/env.h>
35 #include <common/log.h>
36 #include <common/param.h>
37 #include <common/os/system_info.h>
38 #include <common/os/filesystem.h>
39 #include <common/base64.h>
41 #include <core/producer/cg_proxy.h>
42 #include <core/producer/frame_producer.h>
43 #include <core/help/help_repository.h>
44 #include <core/help/help_sink.h>
45 #include <core/help/util.h>
46 #include <core/video_format.h>
47 #include <core/producer/transition/transition_producer.h>
48 #include <core/frame/frame_transform.h>
49 #include <core/producer/stage.h>
50 #include <core/producer/layer.h>
51 #include <core/mixer/mixer.h>
52 #include <core/consumer/output.h>
53 #include <core/thumbnail_generator.h>
54 #include <core/producer/media_info/media_info.h>
55 #include <core/producer/media_info/media_info_repository.h>
56 #include <core/diagnostics/call_context.h>
57 #include <core/diagnostics/osd_graph.h>
58 #include <core/system_info_provider.h>
67 #include <boost/date_time/posix_time/posix_time.hpp>
68 #include <boost/lexical_cast.hpp>
69 #include <boost/algorithm/string.hpp>
70 #include <boost/filesystem.hpp>
71 #include <boost/filesystem/fstream.hpp>
72 #include <boost/regex.hpp>
73 #include <boost/property_tree/xml_parser.hpp>
74 #include <boost/locale.hpp>
75 #include <boost/range/adaptor/transformed.hpp>
76 #include <boost/range/algorithm/copy.hpp>
77 #include <boost/archive/iterators/base64_from_binary.hpp>
78 #include <boost/archive/iterators/insert_linebreaks.hpp>
79 #include <boost/archive/iterators/transform_width.hpp>
81 #include <tbb/concurrent_unordered_map.h>
85 102 [action] Information that [action] has happened
86 101 [action] Information that [action] has happened plus one row of data
88 202 [command] OK [command] has been executed
89 201 [command] OK [command] has been executed, plus one row of data
90 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
92 400 ERROR the command could not be understood
93 401 [command] ERROR invalid/missing channel
94 402 [command] ERROR parameter missing
95 403 [command] ERROR invalid parameter
96 404 [command] ERROR file not found
98 500 FAILED internal error
99 501 [command] FAILED internal error
100 502 [command] FAILED could not read file
101 503 [command] FAILED access denied
103 600 [command] FAILED [command] not implemented
106 namespace caspar { namespace protocol { namespace amcp {
108 using namespace core;
110 std::wstring read_file_base64(const boost::filesystem::path& file)
112 using namespace boost::archive::iterators;
114 boost::filesystem::ifstream filestream(file, std::ios::binary);
119 auto length = boost::filesystem::file_size(file);
120 std::vector<char> bytes;
121 bytes.resize(length);
122 filestream.read(bytes.data(), length);
124 std::string result(to_base64(bytes.data(), length));
125 return std::wstring(result.begin(), result.end());
128 std::wstring read_utf8_file(const boost::filesystem::path& file)
130 std::wstringstream result;
131 boost::filesystem::wifstream filestream(file);
138 result << filestream.rdbuf();
144 std::wstring read_latin1_file(const boost::filesystem::path& file)
146 boost::locale::generator gen;
147 gen.locale_cache_enabled(true);
148 gen.categories(boost::locale::codepage_facet);
150 std::stringstream result_stream;
151 boost::filesystem::ifstream filestream(file);
152 filestream.imbue(gen("en_US.ISO8859-1"));
157 result_stream << filestream.rdbuf();
160 std::string result = result_stream.str();
161 std::wstring widened_result;
163 // The first 255 codepoints in unicode is the same as in latin1
165 result | boost::adaptors::transformed(
166 [](char c) { return static_cast<unsigned char>(c); }),
167 std::back_inserter(widened_result));
169 return widened_result;
172 std::wstring read_file(const boost::filesystem::path& file)
174 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
176 if (!boost::filesystem::exists(file))
181 if (boost::filesystem::file_size(file) >= 3)
183 boost::filesystem::ifstream bom_stream(file);
186 bom_stream.read(header, 3);
189 if (std::memcmp(BOM, header, 3) == 0)
190 return read_utf8_file(file);
193 return read_latin1_file(file);
196 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
198 if (!boost::filesystem::is_regular_file(path))
201 auto media_info = media_info_repo->get(path.wstring());
206 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
208 auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
210 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
211 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
212 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
214 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
215 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
216 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
218 auto str = relativePath.replace_extension(L"").generic_wstring();
219 if (str[0] == '\\' || str[0] == '/')
220 str = std::wstring(str.begin() + 1, str.end());
222 return std::wstring()
224 + L"\" " + media_info->clip_type +
226 + L" " + writeTimeWStr +
227 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
228 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
232 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
234 std::wstringstream replyString;
235 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
236 replyString << MediaInfo(itr->path(), media_info_repo);
238 return boost::to_upper_copy(replyString.str());
241 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
243 std::wstringstream replyString;
245 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
247 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
249 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
251 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
252 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
253 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
255 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
256 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
258 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
260 auto dir = relativePath.parent_path();
261 auto file = boost::to_upper_copy(relativePath.filename().wstring());
262 relativePath = dir / file;
264 auto str = relativePath.replace_extension(L"").generic_wstring();
265 boost::trim_if(str, boost::is_any_of("\\/"));
267 replyString << L"\"" << str
268 << L"\" " << sizeWStr
269 << L" " << writeTimeWStr
273 return replyString.str();
276 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
278 return core::frame_producer_dependencies(
279 channel->frame_factory(),
280 cpplinq::from(ctx.channels)
281 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
283 channel->video_format_desc(),
284 ctx.producer_registry);
289 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
291 sink.short_description(L"Load a media file or resource in the background.");
292 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]})");
294 ->text(L"Loads a producer in the background and prepares it for playout. ")
295 ->text(L"If no layer is specified the default layer index will be used.");
297 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
298 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
300 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
301 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
303 ->code(L"loop")->text(L" will cause the clip to loop.");
305 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
307 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
309 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
310 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
311 sink.para()->text(L"Examples:");
312 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
313 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
314 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
315 sink.example(L">> LOADBG 1-0 MY_FILE");
317 L">> PLAY 1-1 MY_FILE\n"
318 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
319 L"To automatically fade out a layer after a video file has been played to the end");
321 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
323 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
324 ->code(L"filter")->text(L" command.");
327 std::wstring loadbg_command(command_context& ctx)
329 transition_info transitionInfo;
333 std::wstring message;
334 for (size_t n = 0; n < ctx.parameters.size(); ++n)
335 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
337 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)?.*)");
339 if (boost::regex_match(message, what, expr))
341 auto transition = what["TRANSITION"].str();
342 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
343 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
344 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
345 transitionInfo.tweener = tween;
347 if (transition == L"CUT")
348 transitionInfo.type = transition_type::cut;
349 else if (transition == L"MIX")
350 transitionInfo.type = transition_type::mix;
351 else if (transition == L"PUSH")
352 transitionInfo.type = transition_type::push;
353 else if (transition == L"SLIDE")
354 transitionInfo.type = transition_type::slide;
355 else if (transition == L"WIPE")
356 transitionInfo.type = transition_type::wipe;
358 if (direction == L"FROMLEFT")
359 transitionInfo.direction = transition_direction::from_left;
360 else if (direction == L"FROMRIGHT")
361 transitionInfo.direction = transition_direction::from_right;
362 else if (direction == L"LEFT")
363 transitionInfo.direction = transition_direction::from_right;
364 else if (direction == L"RIGHT")
365 transitionInfo.direction = transition_direction::from_left;
368 //Perform loading of the clip
369 core::diagnostics::scoped_call_context save;
370 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
371 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
373 auto channel = ctx.channel.channel;
374 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
376 if (pFP == frame_producer::empty())
377 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
379 bool auto_play = contains_param(L"AUTO", ctx.parameters);
381 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
383 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
385 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
387 return L"202 LOADBG OK\r\n";
390 void load_describer(core::help_sink& sink, const core::help_repository& repo)
392 sink.short_description(L"Load a media file or resource to the foreground.");
393 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
395 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
396 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
397 sink.para()->text(L"Examples:");
398 sink.example(L">> LOAD 1 MY_FILE");
399 sink.example(L">> LOAD 1-1 MY_FILE");
400 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
403 std::wstring load_command(command_context& ctx)
405 core::diagnostics::scoped_call_context save;
406 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
407 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
408 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
409 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
411 return L"202 LOAD OK\r\n";
414 void play_describer(core::help_sink& sink, const core::help_repository& repository)
416 sink.short_description(L"Play a media file or resource.");
417 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
419 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
420 ->text(L") is prepared, it will be executed.");
422 ->text(L"If additional parameters (see ")->see(L"LOADBG")
423 ->text(L") are provided then the provided clip will first be loaded to the background.");
424 sink.para()->text(L"Examples:");
425 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
426 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
427 sink.example(L">> PLAY 1-0 MY_FILE");
428 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
431 std::wstring play_command(command_context& ctx)
433 if (!ctx.parameters.empty())
436 ctx.channel.channel->stage().play(ctx.layer_index());
438 return L"202 PLAY OK\r\n";
441 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
443 sink.short_description(L"Pause playback of a layer.");
444 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
446 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
447 ->text(L" command can be used to resume playback again.");
448 sink.para()->text(L"Examples:");
449 sink.example(L">> PAUSE 1");
450 sink.example(L">> PAUSE 1-1");
453 std::wstring pause_command(command_context& ctx)
455 ctx.channel.channel->stage().pause(ctx.layer_index());
456 return L"202 PAUSE OK\r\n";
459 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
461 sink.short_description(L"Resume playback of a layer.");
462 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
464 ->text(L"Resumes playback of a foreground clip previously paused with the ")
465 ->see(L"PAUSE")->text(L" command.");
466 sink.para()->text(L"Examples:");
467 sink.example(L">> RESUME 1");
468 sink.example(L">> RESUME 1-1");
471 std::wstring resume_command(command_context& ctx)
473 ctx.channel.channel->stage().resume(ctx.layer_index());
474 return L"202 RESUME OK\r\n";
477 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
479 sink.short_description(L"Remove the foreground clip of a layer.");
480 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
482 ->text(L"Removes the foreground clip of the specified layer.");
483 sink.para()->text(L"Examples:");
484 sink.example(L">> STOP 1");
485 sink.example(L">> STOP 1-1");
488 std::wstring stop_command(command_context& ctx)
490 ctx.channel.channel->stage().stop(ctx.layer_index());
491 return L"202 STOP OK\r\n";
494 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
496 sink.short_description(L"Remove all clips of a layer or an entire channel.");
497 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
499 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
500 ->text(L"If no layer is specified then all layers in the specified ")
501 ->code(L"video_channel")->text(L" are cleared.");
502 sink.para()->text(L"Examples:");
503 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
504 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
507 std::wstring clear_command(command_context& ctx)
509 int index = ctx.layer_index(std::numeric_limits<int>::min());
510 if (index != std::numeric_limits<int>::min())
511 ctx.channel.channel->stage().clear(index);
513 ctx.channel.channel->stage().clear();
515 return L"202 CLEAR OK\r\n";
518 void call_describer(core::help_sink& sink, const core::help_repository& repo)
520 sink.short_description(L"Call a method on a producer.");
521 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
523 ->text(L"Calls method on the specified producer with the provided ")
524 ->code(L"param")->text(L" string.");
525 sink.para()->text(L"Examples:");
526 sink.example(L">> CALL 1 LOOP");
527 sink.example(L">> CALL 1-2 SEEK 25");
530 std::wstring call_command(command_context& ctx)
532 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
534 // TODO: because of std::async deferred timed waiting does not work
536 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
537 if (wait_res == std::future_status::timeout)
538 CASPAR_THROW_EXCEPTION(timed_out());*/
540 std::wstringstream replyString;
541 if (result.get().empty())
542 replyString << L"202 CALL OK\r\n";
544 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
546 return replyString.str();
549 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
551 sink.short_description(L"Swap layers between channels.");
552 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
554 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
555 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
556 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
557 sink.para()->text(L"Examples:");
558 sink.example(L">> SWAP 1 2");
559 sink.example(L">> SWAP 1-1 2-3");
560 sink.example(L">> SWAP 1-1 1-2");
561 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
564 std::wstring swap_command(command_context& ctx)
566 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
568 if (ctx.layer_index(-1) != -1)
570 std::vector<std::string> strs;
571 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
573 auto ch1 = ctx.channel.channel;
574 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
576 int l1 = ctx.layer_index();
577 int l2 = boost::lexical_cast<int>(strs.at(1));
579 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
583 auto ch1 = ctx.channel.channel;
584 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
585 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
588 return L"202 SWAP OK\r\n";
591 void add_describer(core::help_sink& sink, const core::help_repository& repo)
593 sink.short_description(L"Add a consumer to a video channel.");
594 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
596 ->text(L"Adds a consumer to the specified video channel. The string ")
597 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
598 ->text(L"If a successful match is found a consumer will be created and added to the ")
599 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
600 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
601 ->see(L"the CasparCG config file")->text(L".");
602 sink.para()->text(L"Examples:");
603 sink.example(L">> ADD 1 DECKLINK 1");
604 sink.example(L">> ADD 1 BLUEFISH 2");
605 sink.example(L">> ADD 1 SCREEN");
606 sink.example(L">> ADD 1 AUDIO");
607 sink.example(L">> ADD 1 IMAGE filename");
608 sink.example(L">> ADD 1 FILE filename.mov");
609 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
610 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
611 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
614 std::wstring add_command(command_context& ctx)
616 replace_placeholders(
617 L"<CLIENT_IP_ADDRESS>",
618 ctx.client->address(),
621 core::diagnostics::scoped_call_context save;
622 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
624 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
625 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
627 return L"202 ADD OK\r\n";
630 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
632 sink.short_description(L"Remove a consumer from a video channel.");
633 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
635 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
636 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
637 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
638 sink.para()->text(L"Examples:");
639 sink.example(L">> REMOVE 1 DECKLINK 1");
640 sink.example(L">> REMOVE 1 BLUEFISH 2");
641 sink.example(L">> REMOVE 1 SCREEN");
642 sink.example(L">> REMOVE 1 AUDIO");
643 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
646 std::wstring remove_command(command_context& ctx)
648 auto index = ctx.layer_index(std::numeric_limits<int>::min());
650 if (index == std::numeric_limits<int>::min())
652 replace_placeholders(
653 L"<CLIENT_IP_ADDRESS>",
654 ctx.client->address(),
657 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
660 ctx.channel.channel->output().remove(index);
662 return L"202 REMOVE OK\r\n";
665 void print_describer(core::help_sink& sink, const core::help_repository& repo)
667 sink.short_description(L"Take a snapshot of a channel.");
668 sink.syntax(L"PRINT [video_channel:int]");
670 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
671 ->code(L"media")->text(L" folder.");
672 sink.para()->text(L"Examples:");
673 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
676 std::wstring print_command(command_context& ctx)
678 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
680 return L"202 PRINT OK\r\n";
683 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
685 sink.short_description(L"Change the log level of the server.");
686 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
687 sink.para()->text(L"Changes the log level of the server.");
688 sink.para()->text(L"Examples:");
689 sink.example(L">> LOG LEVEL trace");
690 sink.example(L">> LOG LEVEL info");
693 std::wstring log_level_command(command_context& ctx)
695 log::set_log_level(ctx.parameters.at(0));
697 return L"202 LOG OK\r\n";
700 void set_describer(core::help_sink& sink, const core::help_repository& repo)
702 sink.short_description(L"Change the value of a channel variable.");
703 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
704 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
706 ->item(L"MODE", L"Changes the video format of the channel.");
707 sink.para()->text(L"Examples:");
708 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL");
711 std::wstring set_command(command_context& ctx)
713 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
714 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
718 auto format_desc = core::video_format_desc(value);
719 if (format_desc.format != core::video_format::invalid)
721 ctx.channel.channel->video_format_desc(format_desc);
722 return L"202 SET MODE OK\r\n";
725 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
728 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
731 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
733 sink.short_description(L"Store a dataset.");
734 sink.syntax(L"DATA STORE [name:string] [data:string]");
735 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
736 sink.para()->text(L"Directories will be created if they do not exist.");
737 sink.para()->text(L"Examples:");
738 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
739 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
742 std::wstring data_store_command(command_context& ctx)
744 std::wstring filename = env::data_folder();
745 filename.append(ctx.parameters[0]);
746 filename.append(L".ftd");
748 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
749 auto found_data_path = find_case_insensitive(data_path);
752 data_path = *found_data_path;
754 if (!boost::filesystem::exists(data_path))
755 boost::filesystem::create_directories(data_path);
757 auto found_filename = find_case_insensitive(filename);
760 filename = *found_filename; // Overwrite case insensitive.
762 boost::filesystem::wofstream datafile(filename);
764 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
766 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
767 datafile << ctx.parameters[1] << std::flush;
770 return L"202 DATA STORE OK\r\n";
773 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
775 sink.short_description(L"Retrieve a dataset.");
776 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
777 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
778 sink.para()->text(L"Examples:");
779 sink.example(L">> DATA RETRIEVE my_data");
780 sink.example(L">> DATA RETRIEVE Folder1/my_data");
783 std::wstring data_retrieve_command(command_context& ctx)
785 std::wstring filename = env::data_folder();
786 filename.append(ctx.parameters[0]);
787 filename.append(L".ftd");
789 std::wstring file_contents;
791 auto found_file = find_case_insensitive(filename);
794 file_contents = read_file(boost::filesystem::path(*found_file));
796 if (file_contents.empty())
797 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
799 std::wstringstream reply;
800 reply << L"201 DATA RETRIEVE OK\r\n";
802 std::wstringstream file_contents_stream(file_contents);
805 bool firstLine = true;
806 while (std::getline(file_contents_stream, line))
820 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
822 sink.short_description(L"List stored datasets.");
823 sink.syntax(L"DATA LIST");
824 sink.para()->text(L"Returns a list of all stored datasets.");
827 std::wstring data_list_command(command_context& ctx)
829 std::wstringstream replyString;
830 replyString << L"200 DATA LIST OK\r\n";
832 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
834 if (boost::filesystem::is_regular_file(itr->path()))
836 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
839 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
841 auto str = relativePath.replace_extension(L"").generic_wstring();
842 if (str[0] == L'\\' || str[0] == L'/')
843 str = std::wstring(str.begin() + 1, str.end());
845 replyString << str << L"\r\n";
849 replyString << L"\r\n";
851 return boost::to_upper_copy(replyString.str());
854 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
856 sink.short_description(L"Remove a stored dataset.");
857 sink.syntax(L"DATA REMOVE [name:string]");
858 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
859 sink.para()->text(L"Examples:");
860 sink.example(L">> DATA REMOVE my_data");
861 sink.example(L">> DATA REMOVE Folder1/my_data");
864 std::wstring data_remove_command(command_context& ctx)
866 std::wstring filename = env::data_folder();
867 filename.append(ctx.parameters[0]);
868 filename.append(L".ftd");
870 if (!boost::filesystem::exists(filename))
871 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
873 if (!boost::filesystem::remove(filename))
874 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
876 return L"201 DATA REMOVE OK\r\n";
879 // Template Graphics Commands
881 int get_and_validate_layer(const std::wstring& layerstring) {
882 int length = layerstring.length();
883 for (int i = 0; i < length; ++i) {
884 if (!std::isdigit(layerstring[i])) {
885 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
889 return boost::lexical_cast<int>(layerstring);
892 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
894 sink.short_description(L"Prepare a template for displaying.");
895 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
897 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
898 ->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.");
899 sink.para()->text(L"Examples:");
900 sink.example(L"CG 1 ADD 10 svtnews/info 1");
903 std::wstring cg_add_command(command_context& ctx)
905 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
907 int layer = get_and_validate_layer(ctx.parameters.at(0));
908 std::wstring label; //_parameters[2]
909 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
910 unsigned int dataIndex = 3;
912 if (ctx.parameters.at(2).length() > 1)
914 label = ctx.parameters.at(2);
917 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
918 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
921 { //read play-on-load-flag
922 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
925 const wchar_t* pDataString = 0;
926 std::wstring dataFromFile;
927 if (ctx.parameters.size() > dataIndex)
929 const std::wstring& dataString = ctx.parameters.at(dataIndex);
931 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
932 pDataString = dataString.c_str();
935 //The data is not an XML-string, it must be a filename
936 std::wstring filename = env::data_folder();
937 filename.append(dataString);
938 filename.append(L".ftd");
940 auto found_file = find_case_insensitive(filename);
944 dataFromFile = read_file(boost::filesystem::path(*found_file));
945 pDataString = dataFromFile.c_str();
950 auto filename = ctx.parameters.at(1);
951 auto proxy = ctx.cg_registry->get_or_create_proxy(
952 spl::make_shared_ptr(ctx.channel.channel),
953 get_producer_dependencies(ctx.channel.channel, ctx),
954 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
957 if (proxy == core::cg_proxy::empty())
958 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
960 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
962 return L"202 CG OK\r\n";
965 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
967 sink.short_description(L"Play and display a template.");
968 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
969 sink.para()->text(L"Plays and displays the template in the specified layer.");
970 sink.para()->text(L"Examples:");
971 sink.example(L"CG 1 PLAY 0");
974 std::wstring cg_play_command(command_context& ctx)
976 int layer = get_and_validate_layer(ctx.parameters.at(0));
977 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
979 return L"202 CG OK\r\n";
982 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
984 sink.short_description(L"Stop and remove a template.");
985 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
987 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
988 ->text(L" in that the template gets a chance to animate out when it is stopped.");
989 sink.para()->text(L"Examples:");
990 sink.example(L"CG 1 STOP 0");
993 std::wstring cg_stop_command(command_context& ctx)
995 int layer = get_and_validate_layer(ctx.parameters.at(0));
996 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->stop(layer, 0);
998 return L"202 CG OK\r\n";
1001 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1003 sink.short_description(LR"(Trigger a "continue" in a template.)");
1004 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1006 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1007 ->text(L"This is used to control animations that has multiple discreet steps.");
1008 sink.para()->text(L"Examples:");
1009 sink.example(L"CG 1 NEXT 0");
1012 std::wstring cg_next_command(command_context& ctx)
1014 int layer = get_and_validate_layer(ctx.parameters.at(0));
1015 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->next(layer);
1017 return L"202 CG OK\r\n";
1020 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1022 sink.short_description(L"Remove a template.");
1023 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1024 sink.para()->text(L"Removes the template from the specified layer.");
1025 sink.para()->text(L"Examples:");
1026 sink.example(L"CG 1 REMOVE 0");
1029 std::wstring cg_remove_command(command_context& ctx)
1031 int layer = get_and_validate_layer(ctx.parameters.at(0));
1032 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->remove(layer);
1034 return L"202 CG OK\r\n";
1037 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1039 sink.short_description(L"Remove all templates on a video layer.");
1040 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1041 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1042 sink.para()->text(L"Examples:");
1043 sink.example(L"CG 1 CLEAR");
1046 std::wstring cg_clear_command(command_context& ctx)
1048 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1050 return L"202 CG OK\r\n";
1053 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1055 sink.short_description(L"Update a template with new data.");
1056 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1057 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.");
1060 std::wstring cg_update_command(command_context& ctx)
1062 int layer = get_and_validate_layer(ctx.parameters.at(0));
1064 std::wstring dataString = ctx.parameters.at(1);
1065 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1067 //The data is not XML or Json, it must be a filename
1068 std::wstring filename = env::data_folder();
1069 filename.append(dataString);
1070 filename.append(L".ftd");
1072 dataString = read_file(boost::filesystem::path(filename));
1075 ctx.cg_registry->get_proxy(
1076 spl::make_shared_ptr(ctx.channel.channel),
1077 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1078 ->update(layer, dataString);
1080 return L"202 CG OK\r\n";
1083 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1085 sink.short_description(L"Invoke a method/label on a template.");
1086 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1087 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1088 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1091 std::wstring cg_invoke_command(command_context& ctx)
1093 std::wstringstream replyString;
1094 replyString << L"201 CG OK\r\n";
1095 int layer = get_and_validate_layer(ctx.parameters.at(0));
1096 auto result = ctx.cg_registry->get_proxy(
1097 spl::make_shared_ptr(ctx.channel.channel),
1098 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1099 ->invoke(layer, ctx.parameters.at(1));
1100 replyString << result << L"\r\n";
1102 return replyString.str();
1105 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1107 sink.short_description(L"Get information about a running template or the template host.");
1108 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1109 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1110 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1113 std::wstring cg_info_command(command_context& ctx)
1115 std::wstringstream replyString;
1116 replyString << L"201 CG OK\r\n";
1118 if (ctx.parameters.empty())
1120 auto info = ctx.cg_registry->get_proxy(
1121 spl::make_shared_ptr(ctx.channel.channel),
1122 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1123 ->template_host_info();
1124 replyString << info << L"\r\n";
1128 int layer = get_and_validate_layer(ctx.parameters.at(0));
1129 auto desc = ctx.cg_registry->get_proxy(
1130 spl::make_shared_ptr(ctx.channel.channel),
1131 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1132 ->description(layer);
1134 replyString << desc << L"\r\n";
1137 return replyString.str();
1142 core::frame_transform get_current_transform(command_context& ctx)
1144 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1147 template<typename Func>
1148 std::wstring reply_value(command_context& ctx, const Func& extractor)
1150 auto value = extractor(get_current_transform(ctx));
1152 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1155 class transforms_applier
1157 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1159 std::vector<stage::transform_tuple_t> transforms_;
1160 command_context& ctx_;
1163 transforms_applier(command_context& ctx)
1166 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1169 ctx.parameters.pop_back();
1172 void add(stage::transform_tuple_t&& transform)
1174 transforms_.push_back(std::move(transform));
1177 void commit_deferred()
1179 ctx_.channel.channel->stage().apply_transforms(
1180 std::move(deferred_transforms_[ctx_.channel_index]));
1187 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1188 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1191 ctx_.channel.channel->stage().apply_transforms(transforms_);
1194 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1196 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1198 sink.short_description(L"Let a layer act as alpha for the one obove.");
1199 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1201 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1202 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1203 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1204 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1205 ->text(L"instead it will be used as the key for the layer above.");
1206 sink.para()->text(L"Examples:");
1207 sink.example(L">> MIXER 1-0 KEYER 1");
1209 L">> MIXER 1-0 KEYER\n"
1210 L"<< 201 MIXER OK\n"
1211 L"<< 1", L"to retrieve the current state");
1214 std::wstring mixer_keyer_command(command_context& ctx)
1216 if (ctx.parameters.empty())
1217 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1219 transforms_applier transforms(ctx);
1220 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1221 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1223 transform.image_transform.is_key = value;
1228 return L"202 MIXER OK\r\n";
1231 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1233 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1235 sink.short_description(L"Enable chroma keying on a layer.");
1236 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1238 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1239 sink.para()->text(L"Examples:");
1240 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1241 sink.example(L">> MIXER 1-1 CHROMA none");
1243 L">> MIXER 1-1 BLEND\n"
1244 L"<< 201 MIXER OK\n"
1245 L"<< SCREEN", L"for getting the current blend mode");
1248 std::wstring mixer_chroma_command(command_context& ctx)
1250 if (ctx.parameters.empty())
1252 auto chroma = get_current_transform(ctx).image_transform.chroma;
1253 return L"201 MIXER OK\r\n"
1254 + core::get_chroma_mode(chroma.key) + L" "
1255 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1256 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1257 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1260 transforms_applier transforms(ctx);
1261 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1262 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1264 core::chroma chroma;
1265 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1267 if (chroma.key != core::chroma::type::none)
1269 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1270 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1271 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1274 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1276 transform.image_transform.chroma = chroma;
1278 }, duration, tween));
1281 return L"202 MIXER OK\r\n";
1284 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1286 sink.short_description(L"Set the blend mode for a layer.");
1287 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1289 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1290 ->text(L"If no argument is given the current blend mode is returned.");
1292 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1293 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1294 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1295 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1296 sink.para()->text(L"Examples:");
1297 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1299 L">> MIXER 1-1 BLEND\n"
1300 L"<< 201 MIXER OK\n"
1301 L"<< SCREEN", L"for getting the current blend mode");
1302 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1305 std::wstring mixer_blend_command(command_context& ctx)
1307 if (ctx.parameters.empty())
1308 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1310 transforms_applier transforms(ctx);
1311 auto value = get_blend_mode(ctx.parameters.at(0));
1312 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1314 transform.image_transform.blend_mode = value;
1319 return L"202 MIXER OK\r\n";
1322 template<typename Getter, typename Setter>
1323 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1325 if (ctx.parameters.empty())
1326 return reply_value(ctx, getter);
1328 transforms_applier transforms(ctx);
1329 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1330 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1331 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1333 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1335 setter(transform, value);
1337 }, duration, tween));
1340 return L"202 MIXER OK\r\n";
1343 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1345 sink.short_description(L"Change the opacity of a layer.");
1346 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1347 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1348 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1349 sink.para()->text(L"Examples:");
1350 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1352 L">> MIXER 1-0 OPACITY\n"
1353 L"<< 201 MIXER OK\n"
1354 L"<< 0.5", L"to retrieve the current opacity");
1357 std::wstring mixer_opacity_command(command_context& ctx)
1359 return single_double_animatable_mixer_command(
1361 [](const frame_transform& t) { return t.image_transform.opacity; },
1362 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1365 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1367 sink.short_description(L"Change the brightness of a layer.");
1368 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1369 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1370 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1371 sink.para()->text(L"Examples:");
1372 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1374 L">> MIXER 1-0 BRIGHTNESS\n"
1375 L"<< 201 MIXER OK\n"
1376 L"0.5", L"to retrieve the current brightness");
1379 std::wstring mixer_brightness_command(command_context& ctx)
1381 return single_double_animatable_mixer_command(
1383 [](const frame_transform& t) { return t.image_transform.brightness; },
1384 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1387 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1389 sink.short_description(L"Change the saturation of a layer.");
1390 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1391 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1392 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1393 sink.para()->text(L"Examples:");
1394 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1396 L">> MIXER 1-0 SATURATION\n"
1397 L"<< 201 MIXER OK\n"
1398 L"<< 0.5", L"to retrieve the current saturation");
1401 std::wstring mixer_saturation_command(command_context& ctx)
1403 return single_double_animatable_mixer_command(
1405 [](const frame_transform& t) { return t.image_transform.saturation; },
1406 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1409 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1411 sink.short_description(L"Change the contrast of a layer.");
1412 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1413 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1414 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1415 sink.para()->text(L"Examples:");
1416 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1418 L">> MIXER 1-0 CONTRAST\n"
1419 L"<< 201 MIXER OK\n"
1420 L"<< 0.5", L"to retrieve the current contrast");
1423 std::wstring mixer_contrast_command(command_context& ctx)
1425 return single_double_animatable_mixer_command(
1427 [](const frame_transform& t) { return t.image_transform.contrast; },
1428 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1431 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1433 sink.short_description(L"Adjust the video levels of a layer.");
1434 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} LEVELS {[min-input:float] [max-input:float] [gamma:float] [min-output:float] [max-output:float]" + ANIMATION_SYNTAX);
1436 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1438 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1439 ->item(L"gamma", L"Adjusts the gamma of the image.")
1440 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1441 sink.para()->text(L"Examples:");
1442 sink.example(L">> MIXER 1-10 LEVELS 0.0627 0.922 1 0 1 25 easeinsine", L"for stretching 16-235 video to 0-255 video");
1443 sink.example(L">> MIXER 1-10 LEVELS 0 1 1 0.0627 0.922 25 easeinsine", L"for compressing 0-255 video to 16-235 video");
1444 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1446 L">> MIXER 1-10 LEVELS\n"
1447 L"<< 201 MIXER OK\n"
1448 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1451 std::wstring mixer_levels_command(command_context& ctx)
1453 if (ctx.parameters.empty())
1455 auto levels = get_current_transform(ctx).image_transform.levels;
1456 return L"201 MIXER OK\r\n"
1457 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1458 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1459 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1460 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1461 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1464 transforms_applier transforms(ctx);
1466 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1467 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1468 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1469 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1470 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1471 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1472 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1474 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1476 transform.image_transform.levels = value;
1478 }, duration, tween));
1481 return L"202 MIXER OK\r\n";
1484 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1486 sink.short_description(L"Change the fill position and scale of a layer.");
1487 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1489 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1490 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1491 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1492 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1494 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1495 ->text(L"You set the left edge to full right => 1 and the width to 0. So this give you the start-coordinates of 1 0 0 1.");
1496 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1498 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1499 ->text(L"if you want to do a smaller window. If, for instance you want to have a window of half the size of your screen, ")
1500 ->text(L"you set with and height to 0.5. If you want to center it you set left and top edge to 0.25 so you will get the arguments 0.25 0.25 0.5 0.5");
1502 ->item(L"x", L"The new x position, 0 = left edge of monitor, 0.5 = middle of monitor, 1.0 = right edge of monitor. Higher and lower values allowed.")
1503 ->item(L"y", L"The new y position, 0 = top edge of monitor, 0.5 = middle of monitor, 1.0 = bottom edge of monitor. Higher and lower values allowed.")
1504 ->item(L"x-scale", L"The new x scale, 1 = 1x the screen width, 0.5 = half the screen width. Higher and lower values allowed. Negative values flips the layer.")
1505 ->item(L"y-scale", L"The new y scale, 1 = 1x the screen height, 0.5 = half the screen height. Higher and lower values allowed. Negative values flips the layer.");
1506 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1507 sink.para()->text(L"Examples:");
1508 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1510 L">> MIXER 1-0 FILL\n"
1511 L"<< 201 MIXER OK\n"
1512 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1515 std::wstring mixer_fill_command(command_context& ctx)
1517 if (ctx.parameters.empty())
1519 auto transform = get_current_transform(ctx).image_transform;
1520 auto translation = transform.fill_translation;
1521 auto scale = transform.fill_scale;
1522 return L"201 MIXER OK\r\n"
1523 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1524 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1525 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1526 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1529 transforms_applier transforms(ctx);
1530 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1531 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1532 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1533 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1534 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1535 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1537 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1539 transform.image_transform.fill_translation[0] = x;
1540 transform.image_transform.fill_translation[1] = y;
1541 transform.image_transform.fill_scale[0] = x_s;
1542 transform.image_transform.fill_scale[1] = y_s;
1544 }, duration, tween));
1547 return L"202 MIXER OK\r\n";
1550 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1552 sink.short_description(L"Change the clipping viewport of a layer.");
1553 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1555 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1556 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1557 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1559 ->item(L"x", L"The new x position, 0 = left edge of monitor, 0.5 = middle of monitor, 1.0 = right edge of monitor. Higher and lower values allowed.")
1560 ->item(L"y", L"The new y position, 0 = top edge of monitor, 0.5 = middle of monitor, 1.0 = bottom edge of monitor. Higher and lower values allowed.")
1561 ->item(L"width", L"The new width, 1 = 1x the screen width, 0.5 = half the screen width. Higher and lower values allowed. Negative values flips the layer.")
1562 ->item(L"height", L"The new height, 1 = 1x the screen height, 0.5 = half the screen height. Higher and lower values allowed. Negative values flips the layer.");
1563 sink.para()->text(L"Examples:");
1564 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1566 L">> MIXER 1-0 CLIP\n"
1567 L"<< 201 MIXER OK\n"
1568 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1571 std::wstring mixer_clip_command(command_context& ctx)
1573 if (ctx.parameters.empty())
1575 auto transform = get_current_transform(ctx).image_transform;
1576 auto translation = transform.clip_translation;
1577 auto scale = transform.clip_scale;
1579 return L"201 MIXER OK\r\n"
1580 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1581 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1582 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1583 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1586 transforms_applier transforms(ctx);
1587 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1588 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1589 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1590 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1591 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1592 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1594 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1596 transform.image_transform.clip_translation[0] = x;
1597 transform.image_transform.clip_translation[1] = y;
1598 transform.image_transform.clip_scale[0] = x_s;
1599 transform.image_transform.clip_scale[1] = y_s;
1601 }, duration, tween));
1604 return L"202 MIXER OK\r\n";
1607 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1609 sink.short_description(L"Change the anchor point of a layer.");
1610 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1611 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1613 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1614 ->text(L" will be done from.");
1616 ->item(L"x", L"The x anchor point, 0 = left edge of layer, 0.5 = middle of layer, 1.0 = right edge of layer. Higher and lower values allowed.")
1617 ->item(L"y", L"The y anchor point, 0 = top edge of layer, 0.5 = middle of layer, 1.0 = bottom edge of layer. Higher and lower values allowed.");
1618 sink.para()->text(L"Examples:");
1619 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1621 L">> MIXER 1-10 ANCHOR\n"
1622 L"<< 201 MIXER OK\n"
1623 L"<< 0.5 0.6", L"gets the anchor point");
1626 std::wstring mixer_anchor_command(command_context& ctx)
1628 if (ctx.parameters.empty())
1630 auto transform = get_current_transform(ctx).image_transform;
1631 auto anchor = transform.anchor;
1632 return L"201 MIXER OK\r\n"
1633 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1634 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1637 transforms_applier transforms(ctx);
1638 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1639 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1640 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1641 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1643 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1645 transform.image_transform.anchor[0] = x;
1646 transform.image_transform.anchor[1] = y;
1648 }, duration, tween));
1651 return L"202 MIXER OK\r\n";
1654 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1656 sink.short_description(L"Crop a layer.");
1657 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CROP {[left-edge:float] [top-edge:float] [right-edge:float] [bottom-edge:float]" + ANIMATION_SYNTAX);
1659 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1660 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1661 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1663 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1664 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1665 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1666 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1667 sink.para()->text(L"Examples:");
1668 sink.example(L">> MIXER 1-0 CROP 0.25 0.25 0.75 0.75 25 easeinsine", L"leaving a 25% crop around the edges");
1670 L">> MIXER 1-0 CROP\n"
1671 L"<< 201 MIXER OK\n"
1672 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1675 std::wstring mixer_crop_command(command_context& ctx)
1677 if (ctx.parameters.empty())
1679 auto crop = get_current_transform(ctx).image_transform.crop;
1680 return L"201 MIXER OK\r\n"
1681 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1682 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1683 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1684 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1687 transforms_applier transforms(ctx);
1688 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1689 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1690 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1691 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1692 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1693 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1695 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1697 transform.image_transform.crop.ul[0] = ul_x;
1698 transform.image_transform.crop.ul[1] = ul_y;
1699 transform.image_transform.crop.lr[0] = lr_x;
1700 transform.image_transform.crop.lr[1] = lr_y;
1702 }, duration, tween));
1705 return L"202 MIXER OK\r\n";
1708 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1710 sink.short_description(L"Rotate a layer.");
1711 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1713 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1714 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1715 sink.para()->text(L"Examples:");
1716 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1718 L">> MIXER 1-0 ROTATION\n"
1719 L"<< 201 MIXER OK\n"
1720 L"<< 45", L"to retrieve the current angle");
1723 std::wstring mixer_rotation_command(command_context& ctx)
1725 static const double PI = 3.141592653589793;
1727 return single_double_animatable_mixer_command(
1729 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1730 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1733 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1735 sink.short_description(L"Adjust the perspective transform of a layer.");
1736 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} PERSPECTIVE {[top-left-x:float] [top-left-y:float] [top-right-x:float] [top-right-y:float] [bottom-right-x:float] [bottom-right-y:float] [bottom-left-x:float] [bottom-left-y:float]" + ANIMATION_SYNTAX);
1738 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1740 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1741 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1742 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1743 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1744 sink.para()->text(L"Examples:");
1745 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1747 L">> MIXER 1-10 PERSPECTIVE\n"
1748 L"<< 201 MIXER OK\n"
1749 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1752 std::wstring mixer_perspective_command(command_context& ctx)
1754 if (ctx.parameters.empty())
1756 auto perspective = get_current_transform(ctx).image_transform.perspective;
1759 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1760 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1761 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1762 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1763 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1764 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1765 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1766 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1769 transforms_applier transforms(ctx);
1770 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1771 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1772 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1773 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1774 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1775 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1776 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1777 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1778 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1779 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1781 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1783 transform.image_transform.perspective.ul[0] = ul_x;
1784 transform.image_transform.perspective.ul[1] = ul_y;
1785 transform.image_transform.perspective.ur[0] = ur_x;
1786 transform.image_transform.perspective.ur[1] = ur_y;
1787 transform.image_transform.perspective.lr[0] = lr_x;
1788 transform.image_transform.perspective.lr[1] = lr_y;
1789 transform.image_transform.perspective.ll[0] = ll_x;
1790 transform.image_transform.perspective.ll[1] = ll_y;
1792 }, duration, tween));
1795 return L"202 MIXER OK\r\n";
1798 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1800 sink.short_description(L"Enable or disable mipmapping for a layer.");
1801 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1803 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1804 ->text(L"If no argument is given the current state is returned.");
1805 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1806 sink.para()->text(L"Examples:");
1807 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1809 L">> MIXER 1-10 MIPMAP\n"
1810 L"<< 201 MIXER OK\n"
1811 L"<< 1", L"for getting the current state");
1814 std::wstring mixer_mipmap_command(command_context& ctx)
1816 if (ctx.parameters.empty())
1817 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1819 transforms_applier transforms(ctx);
1820 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1821 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1823 transform.image_transform.use_mipmap = value;
1828 return L"202 MIXER OK\r\n";
1831 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1833 sink.short_description(L"Change the volume of a layer.");
1834 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1835 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1836 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1837 sink.para()->text(L"Examples:");
1838 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1839 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1841 L">> MIXER 1-0 VOLUME\n"
1842 L"<< 201 MIXER OK\n"
1843 L"<< 0.8", L"to retrieve the current volume");
1846 std::wstring mixer_volume_command(command_context& ctx)
1848 return single_double_animatable_mixer_command(
1850 [](const frame_transform& t) { return t.audio_transform.volume; },
1851 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1854 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1856 sink.short_description(L"Change the volume of an entire channel.");
1857 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1858 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1859 sink.para()->text(L"Examples:");
1860 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1861 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1862 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1865 std::wstring mixer_mastervolume_command(command_context& ctx)
1867 if (ctx.parameters.empty())
1869 auto volume = ctx.channel.channel->mixer().get_master_volume();
1870 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1873 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1874 ctx.channel.channel->mixer().set_master_volume(master_volume);
1876 return L"202 MIXER OK\r\n";
1879 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1881 sink.short_description(L"Create a grid of video layers.");
1882 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1884 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1885 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1886 sink.para()->text(L"Examples:");
1887 sink.example(L">> MIXER 1 GRID 2");
1890 std::wstring mixer_grid_command(command_context& ctx)
1892 transforms_applier transforms(ctx);
1893 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1894 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1895 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1896 double delta = 1.0 / static_cast<double>(n);
1897 for (int x = 0; x < n; ++x)
1899 for (int y = 0; y < n; ++y)
1901 int index = x + y*n + 1;
1902 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1904 transform.image_transform.fill_translation[0] = x*delta;
1905 transform.image_transform.fill_translation[1] = y*delta;
1906 transform.image_transform.fill_scale[0] = delta;
1907 transform.image_transform.fill_scale[1] = delta;
1908 transform.image_transform.clip_translation[0] = x*delta;
1909 transform.image_transform.clip_translation[1] = y*delta;
1910 transform.image_transform.clip_scale[0] = delta;
1911 transform.image_transform.clip_scale[1] = delta;
1913 }, duration, tween));
1918 return L"202 MIXER OK\r\n";
1921 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1923 sink.short_description(L"Commit all deferred mixer transforms.");
1924 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1925 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1926 sink.para()->text(L"Examples:");
1928 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1929 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1930 L">> MIXER 1 COMMIT");
1933 std::wstring mixer_commit_command(command_context& ctx)
1935 transforms_applier transforms(ctx);
1936 transforms.commit_deferred();
1938 return L"202 MIXER OK\r\n";
1941 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1943 sink.short_description(L"Clear all transformations on a channel or layer.");
1944 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1945 sink.para()->text(L"Clears all transformations on a channel or layer.");
1946 sink.para()->text(L"Examples:");
1947 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1948 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1951 std::wstring mixer_clear_command(command_context& ctx)
1953 int layer = ctx.layer_id;
1956 ctx.channel.channel->stage().clear_transforms();
1958 ctx.channel.channel->stage().clear_transforms(layer);
1960 return L"202 MIXER OK\r\n";
1963 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1965 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
1966 sink.syntax(L"CHANNEL_GRID");
1967 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
1969 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
1970 ->code(L"casparcg.config")->text(L" for this to work correctly.");
1973 std::wstring channel_grid_command(command_context& ctx)
1976 auto self = ctx.channels.back();
1978 core::diagnostics::scoped_call_context save;
1979 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
1981 std::vector<std::wstring> params;
1982 params.push_back(L"SCREEN");
1983 params.push_back(L"0");
1984 params.push_back(L"NAME");
1985 params.push_back(L"Channel Grid Window");
1986 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
1988 self.channel->output().add(screen);
1990 for (auto& channel : ctx.channels)
1992 if (channel.channel != self.channel)
1994 core::diagnostics::call_context::for_thread().layer = index;
1995 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
1996 self.channel->stage().load(index, producer, false);
1997 self.channel->stage().play(index);
2002 auto num_channels = ctx.channels.size() - 1;
2003 int square_side_length = std::ceil(std::sqrt(num_channels));
2005 ctx.channel_index = self.channel->index();
2007 ctx.parameters.clear();
2008 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2009 mixer_grid_command(ctx);
2011 return L"202 CHANNEL_GRID OK\r\n";
2014 // Thumbnail Commands
2016 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2018 sink.short_description(L"List all thumbnails.");
2019 sink.syntax(L"THUMBNAIL LIST");
2020 sink.para()->text(L"Lists all thumbnails.");
2021 sink.para()->text(L"Examples:");
2023 L">> THUMBNAIL LIST\n"
2024 L"<< 200 THUMBNAIL LIST OK\n"
2025 L"<< \"AMB\" 20130301T124409 1149\n"
2026 L"<< \"foo/bar\" 20130523T234001 244");
2029 std::wstring thumbnail_list_command(command_context& ctx)
2031 std::wstringstream replyString;
2032 replyString << L"200 THUMBNAIL LIST OK\r\n";
2034 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2036 if (boost::filesystem::is_regular_file(itr->path()))
2038 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2041 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
2043 auto str = relativePath.replace_extension(L"").generic_wstring();
2044 if (str[0] == '\\' || str[0] == '/')
2045 str = std::wstring(str.begin() + 1, str.end());
2047 auto mtime = boost::filesystem::last_write_time(itr->path());
2048 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2049 auto file_size = boost::filesystem::file_size(itr->path());
2051 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2055 replyString << L"\r\n";
2057 return boost::to_upper_copy(replyString.str());
2060 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2062 sink.short_description(L"Retrieve a thumbnail.");
2063 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2064 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2065 sink.para()->text(L"Examples:");
2067 L">> THUMBNAIL RETRIEVE foo/bar\n"
2068 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2069 L"<< ...base64 data...");
2072 std::wstring thumbnail_retrieve_command(command_context& ctx)
2074 std::wstring filename = env::thumbnails_folder();
2075 filename.append(ctx.parameters.at(0));
2076 filename.append(L".png");
2078 std::wstring file_contents;
2080 auto found_file = find_case_insensitive(filename);
2083 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2085 if (file_contents.empty())
2086 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2088 std::wstringstream reply;
2090 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2091 reply << file_contents;
2096 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2098 sink.short_description(L"Regenerate a thumbnail.");
2099 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2100 sink.para()->text(L"Regenerates a thumbnail.");
2103 std::wstring thumbnail_generate_command(command_context& ctx)
2107 ctx.thumb_gen->generate(ctx.parameters.at(0));
2108 return L"202 THUMBNAIL GENERATE OK\r\n";
2111 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2114 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2116 sink.short_description(L"Regenerate all thumbnails.");
2117 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2118 sink.para()->text(L"Regenerates all thumbnails.");
2121 std::wstring thumbnail_generateall_command(command_context& ctx)
2125 ctx.thumb_gen->generate_all();
2126 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2129 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2134 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2136 sink.short_description(L"Get information about a media file.");
2137 sink.syntax(L"CINF [filename:string]");
2138 sink.para()->text(L"Returns information about a media file.");
2141 std::wstring cinf_command(command_context& ctx)
2144 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2146 auto path = itr->path();
2147 auto file = path.replace_extension(L"").filename().wstring();
2148 if (boost::iequals(file, ctx.parameters.at(0)))
2149 info += MediaInfo(itr->path(), ctx.media_info_repo) + L"\r\n";
2153 CASPAR_THROW_EXCEPTION(file_not_found());
2155 std::wstringstream replyString;
2156 replyString << L"200 CINF OK\r\n";
2157 replyString << info << "\r\n";
2159 return replyString.str();
2162 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2164 sink.short_description(L"List all media files.");
2165 sink.syntax(L"CLS");
2167 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2168 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2171 std::wstring cls_command(command_context& ctx)
2173 std::wstringstream replyString;
2174 replyString << L"200 CLS OK\r\n";
2175 replyString << ListMedia(ctx.media_info_repo);
2176 replyString << L"\r\n";
2177 return boost::to_upper_copy(replyString.str());
2180 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2182 sink.short_description(L"List all templates.");
2183 sink.syntax(L"TLS");
2185 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2186 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2189 std::wstring tls_command(command_context& ctx)
2191 std::wstringstream replyString;
2192 replyString << L"200 TLS OK\r\n";
2194 replyString << ListTemplates(ctx.cg_registry);
2195 replyString << L"\r\n";
2197 return replyString.str();
2200 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2202 sink.short_description(L"Get version information.");
2203 sink.syntax(L"VERSION {[component:string]}");
2204 sink.para()->text(L"Returns the version of specified component.");
2205 sink.para()->text(L"Examples:");
2208 L"<< 201 VERSION OK\n"
2209 L"<< 2.1.0.f207a33 STABLE");
2211 L">> VERSION SERVER\n"
2212 L"<< 201 VERSION OK\n"
2213 L"<< 2.1.0.f207a33 STABLE");
2215 L">> VERSION FLASH\n"
2216 L"<< 201 VERSION OK\n"
2220 std::wstring version_command(command_context& ctx)
2222 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2224 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2226 return L"201 VERSION OK\r\n" + version + L"\r\n";
2229 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2232 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2234 sink.short_description(L"Get a list of the available channels.");
2235 sink.syntax(L"INFO");
2236 sink.para()->text(L"Retrieves a list of the available channels.");
2240 L"<< 1 720p5000 PLAYING\n"
2241 L"<< 2 PAL PLAYING");
2244 std::wstring info_command(command_context& ctx)
2246 std::wstringstream replyString;
2247 // This is needed for backwards compatibility with old clients
2248 replyString << L"200 INFO OK\r\n";
2249 for (size_t n = 0; n < ctx.channels.size(); ++n)
2250 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2251 replyString << L"\r\n";
2252 return replyString.str();
2255 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2257 std::wstringstream replyString;
2259 if (command.empty())
2260 replyString << L"201 INFO OK\r\n";
2262 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2264 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2265 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2266 replyString << L"\r\n";
2267 return replyString.str();
2270 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2272 sink.short_description(L"Get information about a channel or a layer.");
2273 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2274 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2275 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2278 std::wstring info_channel_command(command_context& ctx)
2280 boost::property_tree::wptree info;
2281 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2283 if (layer == std::numeric_limits<int>::min())
2285 info.add_child(L"channel", ctx.channel.channel->info())
2286 .add(L"index", ctx.channel_index);
2290 if (ctx.parameters.size() >= 1)
2292 if (boost::iequals(ctx.parameters.at(0), L"B"))
2293 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2295 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2299 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2303 return create_info_xml_reply(info);
2306 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2308 sink.short_description(L"Get information about a template.");
2309 sink.syntax(L"INFO TEMPLATE [template:string]");
2310 sink.para()->text(L"Gets information about the specified template.");
2313 std::wstring info_template_command(command_context& ctx)
2315 auto filename = ctx.parameters.at(0);
2317 std::wstringstream str;
2318 str << u16(ctx.cg_registry->read_meta_info(filename));
2319 boost::property_tree::wptree info;
2320 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2322 return create_info_xml_reply(info, L"TEMPLATE");
2325 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2327 sink.short_description(L"Get the contents of the configuration used.");
2328 sink.syntax(L"INFO CONFIG");
2329 sink.para()->text(L"Gets the contents of the configuration used.");
2332 std::wstring info_config_command(command_context& ctx)
2334 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2337 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2339 sink.short_description(L"Get information about the paths used.");
2340 sink.syntax(L"INFO PATHS");
2341 sink.para()->text(L"Gets information about the paths used.");
2344 std::wstring info_paths_command(command_context& ctx)
2346 boost::property_tree::wptree info;
2347 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2348 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2350 return create_info_xml_reply(info, L"PATHS");
2353 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2355 sink.short_description(L"Get system information.");
2356 sink.syntax(L"INFO SYSTEM");
2357 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2360 std::wstring info_system_command(command_context& ctx)
2362 boost::property_tree::wptree info;
2364 info.add(L"system.name", caspar::system_product_name());
2365 info.add(L"system.os.description", caspar::os_description());
2366 info.add(L"system.cpu", caspar::cpu_info());
2368 ctx.system_info_repo->fill_information(info);
2370 return create_info_xml_reply(info, L"SYSTEM");
2373 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2375 sink.short_description(L"Get detailed information about all channels.");
2376 sink.syntax(L"INFO SERVER");
2377 sink.para()->text(L"Gets detailed information about all channels.");
2380 std::wstring info_server_command(command_context& ctx)
2382 boost::property_tree::wptree info;
2385 for (auto& channel : ctx.channels)
2386 info.add_child(L"channels.channel", channel.channel->info())
2387 .add(L"index", ++index);
2389 return create_info_xml_reply(info, L"SERVER");
2392 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2394 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2395 sink.syntax(L"INFO QUEUES");
2396 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2399 std::wstring info_queues_command(command_context& ctx)
2401 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2404 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2406 sink.short_description(L"Open the diagnostics window.");
2407 sink.syntax(L"DIAG");
2408 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2411 std::wstring diag_command(command_context& ctx)
2413 core::diagnostics::osd::show_graphs(true);
2415 return L"202 DIAG OK\r\n";
2418 static const int WIDTH = 80;
2420 struct max_width_sink : public core::help_sink
2422 std::size_t max_width = 0;
2424 void begin_item(const std::wstring& name) override
2426 max_width = std::max(name.length(), max_width);
2430 struct short_description_sink : public core::help_sink
2433 std::wstringstream& out;
2435 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2437 void begin_item(const std::wstring& name) override
2439 out << std::left << std::setw(width + 1) << name;
2442 void short_description(const std::wstring& short_description) override
2444 out << short_description << L"\r\n";
2448 struct simple_paragraph_builder : core::paragraph_builder
2450 std::wostringstream out;
2451 std::wstringstream& commit_to;
2453 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2454 ~simple_paragraph_builder()
2456 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2458 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2460 out << std::move(text);
2461 return shared_from_this();
2463 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2464 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2465 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2468 struct simple_definition_list_builder : core::definition_list_builder
2470 std::wstringstream& out;
2472 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2473 ~simple_definition_list_builder()
2478 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2480 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2481 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2482 return shared_from_this();
2486 struct long_description_sink : public core::help_sink
2488 std::wstringstream& out;
2490 long_description_sink(std::wstringstream& out) : out(out) { }
2492 void syntax(const std::wstring& syntax) override
2494 out << L"Syntax:\n";
2495 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2498 spl::shared_ptr<core::paragraph_builder> para() override
2500 return spl::make_shared<simple_paragraph_builder>(out);
2503 spl::shared_ptr<core::definition_list_builder> definitions() override
2505 return spl::make_shared<simple_definition_list_builder>(out);
2508 void example(const std::wstring& code, const std::wstring& caption) override
2510 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2512 if (!caption.empty())
2513 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2518 void begin_item(const std::wstring& name) override
2520 out << name << L"\n\n";
2524 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2526 std::wstringstream result;
2527 result << L"200 " << help_command << L" OK\r\n";
2528 max_width_sink width;
2529 ctx.help_repo->help(tags, width);
2530 short_description_sink sink(width.max_width, result);
2531 sink.width = width.max_width;
2532 ctx.help_repo->help(tags, sink);
2534 return result.str();
2537 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2539 std::wstringstream result;
2540 result << L"201 " << help_command << L" OK\r\n";
2541 auto joined = boost::join(ctx.parameters, L" ");
2542 long_description_sink sink(result);
2543 ctx.help_repo->help(tags, joined, sink);
2545 return result.str();
2548 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2550 sink.short_description(L"Show online help for AMCP commands.");
2551 sink.syntax(LR"(HELP {[command:string]})");
2552 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2553 sink.example(L">> HELP", L"Shows a list of commands.");
2554 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2557 std::wstring help_command(command_context& ctx)
2559 if (ctx.parameters.size() == 0)
2560 return create_help_list(L"HELP", ctx, { L"AMCP" });
2562 return create_help_details(L"HELP", ctx, { L"AMCP" });
2565 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2567 sink.short_description(L"Show online help for producers.");
2568 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2569 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2570 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2571 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2574 std::wstring help_producer_command(command_context& ctx)
2576 if (ctx.parameters.size() == 0)
2577 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2579 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2582 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2584 sink.short_description(L"Show online help for consumers.");
2585 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2586 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2587 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2588 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2591 std::wstring help_consumer_command(command_context& ctx)
2593 if (ctx.parameters.size() == 0)
2594 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2596 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2599 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2601 sink.short_description(L"Disconnect the session.");
2602 sink.syntax(L"BYE");
2604 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2605 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2608 std::wstring bye_command(command_context& ctx)
2610 ctx.client->disconnect();
2614 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2616 sink.short_description(L"Shutdown the server.");
2617 sink.syntax(L"KILL");
2618 sink.para()->text(L"Shuts the server down.");
2621 std::wstring kill_command(command_context& ctx)
2623 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2624 return L"202 KILL OK\r\n";
2627 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2629 sink.short_description(L"Shutdown the server with restart exit code.");
2630 sink.syntax(L"RESTART");
2632 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2633 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2636 std::wstring restart_command(command_context& ctx)
2638 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2639 return L"202 RESTART OK\r\n";
2642 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2644 sink.short_description(L"Lock or unlock access to a channel.");
2645 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2646 sink.para()->text(L"Allows for exclusive access to a channel.");
2647 sink.para()->text(L"Examples:");
2648 sink.example(L"LOCK 1 ACQUIRE secret");
2649 sink.example(L"LOCK 1 RELEASE");
2650 sink.example(L"LOCK 1 CLEAR");
2653 std::wstring lock_command(command_context& ctx)
2655 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2656 auto lock = ctx.channels.at(channel_index).lock;
2657 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2659 if (command == L"ACQUIRE")
2661 std::wstring lock_phrase = ctx.parameters.at(2);
2663 //TODO: read options
2665 //just lock one channel
2666 if (!lock->try_lock(lock_phrase, ctx.client))
2667 return L"503 LOCK ACQUIRE FAILED\r\n";
2669 return L"202 LOCK ACQUIRE OK\r\n";
2671 else if (command == L"RELEASE")
2673 lock->release_lock(ctx.client);
2674 return L"202 LOCK RELEASE OK\r\n";
2676 else if (command == L"CLEAR")
2678 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2679 std::wstring client_override_phrase;
2681 if (!override_phrase.empty())
2682 client_override_phrase = ctx.parameters.at(2);
2684 //just clear one channel
2685 if (client_override_phrase != override_phrase)
2686 return L"503 LOCK CLEAR FAILED\r\n";
2688 lock->clear_locks();
2690 return L"202 LOCK CLEAR OK\r\n";
2693 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2696 void register_commands(amcp_command_repository& repo)
2698 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2699 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2700 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2701 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2702 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2703 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2704 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2705 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2706 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2707 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2708 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2709 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2710 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2711 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2712 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2714 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2715 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2716 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2717 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2719 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2720 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2721 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2722 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2723 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2724 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2725 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2726 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2727 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2729 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2730 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2731 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2732 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2733 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2734 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2735 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2736 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2737 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2738 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2739 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2740 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2741 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2742 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2743 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2744 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2745 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2746 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2747 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2748 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2749 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2751 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2752 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2753 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2754 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2756 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2757 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2758 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2759 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2760 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2761 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2762 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2763 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2764 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2765 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2766 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2767 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2768 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2769 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2770 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2771 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2772 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2773 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2774 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2778 }} //namespace caspar