2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Nicklas P Andersson
22 #include "../StdAfx.h"
25 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
28 #include "AMCPCommandsImpl.h"
30 #include "amcp_command_repository.h"
32 #include <common/env.h>
34 #include <common/log.h>
35 #include <common/param.h>
36 #include <common/os/system_info.h>
37 #include <common/os/filesystem.h>
38 #include <common/base64.h>
40 #include <core/producer/cg_proxy.h>
41 #include <core/producer/frame_producer.h>
42 #include <core/help/help_repository.h>
43 #include <core/help/help_sink.h>
44 #include <core/help/util.h>
45 #include <core/video_format.h>
46 #include <core/producer/transition/transition_producer.h>
47 #include <core/frame/frame_transform.h>
48 #include <core/producer/stage.h>
49 #include <core/producer/layer.h>
50 #include <core/mixer/mixer.h>
51 #include <core/consumer/output.h>
52 #include <core/thumbnail_generator.h>
53 #include <core/producer/media_info/media_info.h>
54 #include <core/producer/media_info/media_info_repository.h>
55 #include <core/diagnostics/call_context.h>
56 #include <core/diagnostics/osd_graph.h>
57 #include <core/system_info_provider.h>
66 #include <boost/date_time/posix_time/posix_time.hpp>
67 #include <boost/lexical_cast.hpp>
68 #include <boost/algorithm/string.hpp>
69 #include <boost/filesystem.hpp>
70 #include <boost/filesystem/fstream.hpp>
71 #include <boost/regex.hpp>
72 #include <boost/property_tree/xml_parser.hpp>
73 #include <boost/locale.hpp>
74 #include <boost/range/adaptor/transformed.hpp>
75 #include <boost/range/algorithm/copy.hpp>
76 #include <boost/archive/iterators/base64_from_binary.hpp>
77 #include <boost/archive/iterators/insert_linebreaks.hpp>
78 #include <boost/archive/iterators/transform_width.hpp>
80 #include <tbb/concurrent_unordered_map.h>
84 102 [action] Information that [action] has happened
85 101 [action] Information that [action] has happened plus one row of data
87 202 [command] OK [command] has been executed
88 201 [command] OK [command] has been executed, plus one row of data
89 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
91 400 ERROR the command could not be understood
92 401 [command] ERROR invalid/missing channel
93 402 [command] ERROR parameter missing
94 403 [command] ERROR invalid parameter
95 404 [command] ERROR file not found
97 500 FAILED internal error
98 501 [command] FAILED internal error
99 502 [command] FAILED could not read file
100 503 [command] FAILED access denied
102 600 [command] FAILED [command] not implemented
105 namespace caspar { namespace protocol { namespace amcp {
107 using namespace core;
109 std::wstring read_file_base64(const boost::filesystem::path& file)
111 using namespace boost::archive::iterators;
113 boost::filesystem::ifstream filestream(file, std::ios::binary);
118 auto length = boost::filesystem::file_size(file);
119 std::vector<char> bytes;
120 bytes.resize(length);
121 filestream.read(bytes.data(), length);
123 std::string result(to_base64(bytes.data(), length));
124 return std::wstring(result.begin(), result.end());
127 std::wstring read_utf8_file(const boost::filesystem::path& file)
129 std::wstringstream result;
130 boost::filesystem::wifstream filestream(file);
137 result << filestream.rdbuf();
143 std::wstring read_latin1_file(const boost::filesystem::path& file)
145 boost::locale::generator gen;
146 gen.locale_cache_enabled(true);
147 gen.categories(boost::locale::codepage_facet);
149 std::stringstream result_stream;
150 boost::filesystem::ifstream filestream(file);
151 filestream.imbue(gen("en_US.ISO8859-1"));
156 result_stream << filestream.rdbuf();
159 std::string result = result_stream.str();
160 std::wstring widened_result;
162 // The first 255 codepoints in unicode is the same as in latin1
164 result | boost::adaptors::transformed(
165 [](char c) { return static_cast<unsigned char>(c); }),
166 std::back_inserter(widened_result));
168 return widened_result;
171 std::wstring read_file(const boost::filesystem::path& file)
173 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
175 if (!boost::filesystem::exists(file))
180 if (boost::filesystem::file_size(file) >= 3)
182 boost::filesystem::ifstream bom_stream(file);
185 bom_stream.read(header, 3);
188 if (std::memcmp(BOM, header, 3) == 0)
189 return read_utf8_file(file);
192 return read_latin1_file(file);
195 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
197 if (!boost::filesystem::is_regular_file(path))
200 auto media_info = media_info_repo->get(path.wstring());
205 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
207 auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
209 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
210 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
211 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
213 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
214 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
215 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
217 auto str = relativePath.replace_extension(L"").generic_wstring();
218 if (str[0] == '\\' || str[0] == '/')
219 str = std::wstring(str.begin() + 1, str.end());
221 return std::wstring()
223 + L"\" " + media_info->clip_type +
225 + L" " + writeTimeWStr +
226 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
227 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
231 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
233 std::wstringstream replyString;
234 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
235 replyString << MediaInfo(itr->path(), media_info_repo);
237 return boost::to_upper_copy(replyString.str());
240 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
242 std::wstringstream replyString;
244 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
246 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
248 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
250 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
251 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
252 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
254 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
255 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
257 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
259 auto dir = relativePath.parent_path();
260 auto file = boost::to_upper_copy(relativePath.filename().wstring());
261 relativePath = dir / file;
263 auto str = relativePath.replace_extension(L"").generic_wstring();
264 boost::trim_if(str, boost::is_any_of("\\/"));
266 replyString << L"\"" << str
267 << L"\" " << sizeWStr
268 << L" " << writeTimeWStr
272 return replyString.str();
275 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
277 return core::frame_producer_dependencies(
278 channel->frame_factory(),
279 cpplinq::from(ctx.channels)
280 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
282 channel->video_format_desc(),
283 ctx.producer_registry);
288 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
290 sink.short_description(L"Load a media file or resource in the background.");
291 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]})");
293 ->text(L"Loads a producer in the background and prepares it for playout. ")
294 ->text(L"If no layer is specified the default layer index will be used.");
296 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
297 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
299 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
300 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
302 ->code(L"loop")->text(L" will cause the clip to loop.");
304 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
306 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
308 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
309 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
310 sink.para()->text(L"Examples:");
311 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
312 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
313 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
314 sink.example(L">> LOADBG 1-0 MY_FILE");
316 L">> PLAY 1-1 MY_FILE\n"
317 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
318 L"To automatically fade out a layer after a video file has been played to the end");
320 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
322 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
323 ->code(L"filter")->text(L" command.");
326 std::wstring loadbg_command(command_context& ctx)
328 transition_info transitionInfo;
332 std::wstring message;
333 for (size_t n = 0; n < ctx.parameters.size(); ++n)
334 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
336 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)?.*)");
338 if (boost::regex_match(message, what, expr))
340 auto transition = what["TRANSITION"].str();
341 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
342 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
343 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
344 transitionInfo.tweener = tween;
346 if (transition == L"CUT")
347 transitionInfo.type = transition_type::cut;
348 else if (transition == L"MIX")
349 transitionInfo.type = transition_type::mix;
350 else if (transition == L"PUSH")
351 transitionInfo.type = transition_type::push;
352 else if (transition == L"SLIDE")
353 transitionInfo.type = transition_type::slide;
354 else if (transition == L"WIPE")
355 transitionInfo.type = transition_type::wipe;
357 if (direction == L"FROMLEFT")
358 transitionInfo.direction = transition_direction::from_left;
359 else if (direction == L"FROMRIGHT")
360 transitionInfo.direction = transition_direction::from_right;
361 else if (direction == L"LEFT")
362 transitionInfo.direction = transition_direction::from_right;
363 else if (direction == L"RIGHT")
364 transitionInfo.direction = transition_direction::from_left;
367 //Perform loading of the clip
368 core::diagnostics::scoped_call_context save;
369 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
370 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
372 auto channel = ctx.channel.channel;
373 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
375 if (pFP == frame_producer::empty())
376 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
378 bool auto_play = contains_param(L"AUTO", ctx.parameters);
380 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
382 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
384 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
386 return L"202 LOADBG OK\r\n";
389 void load_describer(core::help_sink& sink, const core::help_repository& repo)
391 sink.short_description(L"Load a media file or resource to the foreground.");
392 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
394 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
395 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
396 sink.para()->text(L"Examples:");
397 sink.example(L">> LOAD 1 MY_FILE");
398 sink.example(L">> LOAD 1-1 MY_FILE");
399 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
402 std::wstring load_command(command_context& ctx)
404 core::diagnostics::scoped_call_context save;
405 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
406 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
407 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
408 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
410 return L"202 LOAD OK\r\n";
413 void play_describer(core::help_sink& sink, const core::help_repository& repository)
415 sink.short_description(L"Play a media file or resource.");
416 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
418 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
419 ->text(L") is prepared, it will be executed.");
421 ->text(L"If additional parameters (see ")->see(L"LOADBG")
422 ->text(L") are provided then the provided clip will first be loaded to the background.");
423 sink.para()->text(L"Examples:");
424 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
425 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
426 sink.example(L">> PLAY 1-0 MY_FILE");
427 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
430 std::wstring play_command(command_context& ctx)
432 if (!ctx.parameters.empty())
435 ctx.channel.channel->stage().play(ctx.layer_index());
437 return L"202 PLAY OK\r\n";
440 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
442 sink.short_description(L"Pause playback of a layer.");
443 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
445 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
446 ->text(L" command can be used to resume playback again.");
447 sink.para()->text(L"Examples:");
448 sink.example(L">> PAUSE 1");
449 sink.example(L">> PAUSE 1-1");
452 std::wstring pause_command(command_context& ctx)
454 ctx.channel.channel->stage().pause(ctx.layer_index());
455 return L"202 PAUSE OK\r\n";
458 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
460 sink.short_description(L"Resume playback of a layer.");
461 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
463 ->text(L"Resumes playback of a foreground clip previously paused with the ")
464 ->see(L"PAUSE")->text(L" command.");
465 sink.para()->text(L"Examples:");
466 sink.example(L">> RESUME 1");
467 sink.example(L">> RESUME 1-1");
470 std::wstring resume_command(command_context& ctx)
472 ctx.channel.channel->stage().resume(ctx.layer_index());
473 return L"202 RESUME OK\r\n";
476 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
478 sink.short_description(L"Remove the foreground clip of a layer.");
479 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
481 ->text(L"Removes the foreground clip of the specified layer.");
482 sink.para()->text(L"Examples:");
483 sink.example(L">> STOP 1");
484 sink.example(L">> STOP 1-1");
487 std::wstring stop_command(command_context& ctx)
489 ctx.channel.channel->stage().stop(ctx.layer_index());
490 return L"202 STOP OK\r\n";
493 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
495 sink.short_description(L"Remove all clips of a layer or an entire channel.");
496 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
498 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
499 ->text(L"If no layer is specified then all layers in the specified ")
500 ->code(L"video_channel")->text(L" are cleared.");
501 sink.para()->text(L"Examples:");
502 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
503 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
506 std::wstring clear_command(command_context& ctx)
508 int index = ctx.layer_index(std::numeric_limits<int>::min());
509 if (index != std::numeric_limits<int>::min())
510 ctx.channel.channel->stage().clear(index);
512 ctx.channel.channel->stage().clear();
514 return L"202 CLEAR OK\r\n";
517 void call_describer(core::help_sink& sink, const core::help_repository& repo)
519 sink.short_description(L"Call a method on a producer.");
520 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
522 ->text(L"Calls method on the specified producer with the provided ")
523 ->code(L"param")->text(L" string.");
524 sink.para()->text(L"Examples:");
525 sink.example(L">> CALL 1 LOOP");
526 sink.example(L">> CALL 1-2 SEEK 25");
529 std::wstring call_command(command_context& ctx)
531 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
533 // TODO: because of std::async deferred timed waiting does not work
535 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
536 if (wait_res == std::future_status::timeout)
537 CASPAR_THROW_EXCEPTION(timed_out());*/
539 std::wstringstream replyString;
540 if (result.get().empty())
541 replyString << L"202 CALL OK\r\n";
543 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
545 return replyString.str();
548 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
550 sink.short_description(L"Swap layers between channels.");
551 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
553 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
554 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
555 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
556 sink.para()->text(L"Examples:");
557 sink.example(L">> SWAP 1 2");
558 sink.example(L">> SWAP 1-1 2-3");
559 sink.example(L">> SWAP 1-1 1-2");
560 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
563 std::wstring swap_command(command_context& ctx)
565 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
567 if (ctx.layer_index(-1) != -1)
569 std::vector<std::string> strs;
570 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
572 auto ch1 = ctx.channel.channel;
573 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
575 int l1 = ctx.layer_index();
576 int l2 = boost::lexical_cast<int>(strs.at(1));
578 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
582 auto ch1 = ctx.channel.channel;
583 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
584 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
587 return L"202 SWAP OK\r\n";
590 void add_describer(core::help_sink& sink, const core::help_repository& repo)
592 sink.short_description(L"Add a consumer to a video channel.");
593 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
595 ->text(L"Adds a consumer to the specified video channel. The string ")
596 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
597 ->text(L"If a successful match is found a consumer will be created and added to the ")
598 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
599 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
600 ->see(L"the CasparCG config file")->text(L".");
601 sink.para()->text(L"Examples:");
602 sink.example(L">> ADD 1 DECKLINK 1");
603 sink.example(L">> ADD 1 BLUEFISH 2");
604 sink.example(L">> ADD 1 SCREEN");
605 sink.example(L">> ADD 1 AUDIO");
606 sink.example(L">> ADD 1 IMAGE filename");
607 sink.example(L">> ADD 1 FILE filename.mov");
608 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
609 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
610 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
613 std::wstring add_command(command_context& ctx)
615 replace_placeholders(
616 L"<CLIENT_IP_ADDRESS>",
617 ctx.client->address(),
620 core::diagnostics::scoped_call_context save;
621 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
623 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
624 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
626 return L"202 ADD OK\r\n";
629 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
631 sink.short_description(L"Remove a consumer from a video channel.");
632 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
634 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
635 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
636 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
637 sink.para()->text(L"Examples:");
638 sink.example(L">> REMOVE 1 DECKLINK 1");
639 sink.example(L">> REMOVE 1 BLUEFISH 2");
640 sink.example(L">> REMOVE 1 SCREEN");
641 sink.example(L">> REMOVE 1 AUDIO");
642 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
645 std::wstring remove_command(command_context& ctx)
647 auto index = ctx.layer_index(std::numeric_limits<int>::min());
649 if (index == std::numeric_limits<int>::min())
651 replace_placeholders(
652 L"<CLIENT_IP_ADDRESS>",
653 ctx.client->address(),
656 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
659 ctx.channel.channel->output().remove(index);
661 return L"202 REMOVE OK\r\n";
664 void print_describer(core::help_sink& sink, const core::help_repository& repo)
666 sink.short_description(L"Take a snapshot of a channel.");
667 sink.syntax(L"PRINT [video_channel:int]");
669 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
670 ->code(L"media")->text(L" folder.");
671 sink.para()->text(L"Examples:");
672 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
675 std::wstring print_command(command_context& ctx)
677 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
679 return L"202 PRINT OK\r\n";
682 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
684 sink.short_description(L"Change the log level of the server.");
685 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
686 sink.para()->text(L"Changes the log level of the server.");
687 sink.para()->text(L"Examples:");
688 sink.example(L">> LOG LEVEL trace");
689 sink.example(L">> LOG LEVEL info");
692 std::wstring log_level_command(command_context& ctx)
694 log::set_log_level(ctx.parameters.at(0));
696 return L"202 LOG OK\r\n";
699 void set_describer(core::help_sink& sink, const core::help_repository& repo)
701 sink.short_description(L"Change the value of a channel variable.");
702 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
703 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
705 ->item(L"MODE", L"Changes the video format of the channel.");
706 sink.para()->text(L"Examples:");
707 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL");
710 std::wstring set_command(command_context& ctx)
712 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
713 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
717 auto format_desc = core::video_format_desc(value);
718 if (format_desc.format != core::video_format::invalid)
720 ctx.channel.channel->video_format_desc(format_desc);
721 return L"202 SET MODE OK\r\n";
724 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
727 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
730 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
732 sink.short_description(L"Store a dataset.");
733 sink.syntax(L"DATA STORE [name:string] [data:string]");
734 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
735 sink.para()->text(L"Directories will be created if they do not exist.");
736 sink.para()->text(L"Examples:");
737 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
738 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
741 std::wstring data_store_command(command_context& ctx)
743 std::wstring filename = env::data_folder();
744 filename.append(ctx.parameters[0]);
745 filename.append(L".ftd");
747 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
748 auto found_data_path = find_case_insensitive(data_path);
751 data_path = *found_data_path;
753 if (!boost::filesystem::exists(data_path))
754 boost::filesystem::create_directories(data_path);
756 auto found_filename = find_case_insensitive(filename);
759 filename = *found_filename; // Overwrite case insensitive.
761 boost::filesystem::wofstream datafile(filename);
763 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
765 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
766 datafile << ctx.parameters[1] << std::flush;
769 return L"202 DATA STORE OK\r\n";
772 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
774 sink.short_description(L"Retrieve a dataset.");
775 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
776 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
777 sink.para()->text(L"Examples:");
778 sink.example(L">> DATA RETRIEVE my_data");
779 sink.example(L">> DATA RETRIEVE Folder1/my_data");
782 std::wstring data_retrieve_command(command_context& ctx)
784 std::wstring filename = env::data_folder();
785 filename.append(ctx.parameters[0]);
786 filename.append(L".ftd");
788 std::wstring file_contents;
790 auto found_file = find_case_insensitive(filename);
793 file_contents = read_file(boost::filesystem::path(*found_file));
795 if (file_contents.empty())
796 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
798 std::wstringstream reply;
799 reply << L"201 DATA RETRIEVE OK\r\n";
801 std::wstringstream file_contents_stream(file_contents);
804 bool firstLine = true;
805 while (std::getline(file_contents_stream, line))
819 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
821 sink.short_description(L"List stored datasets.");
822 sink.syntax(L"DATA LIST");
823 sink.para()->text(L"Returns a list of all stored datasets.");
826 std::wstring data_list_command(command_context& ctx)
828 std::wstringstream replyString;
829 replyString << L"200 DATA LIST OK\r\n";
831 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
833 if (boost::filesystem::is_regular_file(itr->path()))
835 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
838 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
840 auto str = relativePath.replace_extension(L"").generic_wstring();
841 if (str[0] == L'\\' || str[0] == L'/')
842 str = std::wstring(str.begin() + 1, str.end());
844 replyString << str << L"\r\n";
848 replyString << L"\r\n";
850 return boost::to_upper_copy(replyString.str());
853 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
855 sink.short_description(L"Remove a stored dataset.");
856 sink.syntax(L"DATA REMOVE [name:string]");
857 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
858 sink.para()->text(L"Examples:");
859 sink.example(L">> DATA REMOVE my_data");
860 sink.example(L">> DATA REMOVE Folder1/my_data");
863 std::wstring data_remove_command(command_context& ctx)
865 std::wstring filename = env::data_folder();
866 filename.append(ctx.parameters[0]);
867 filename.append(L".ftd");
869 if (!boost::filesystem::exists(filename))
870 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
872 if (!boost::filesystem::remove(filename))
873 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
875 return L"201 DATA REMOVE OK\r\n";
878 // Template Graphics Commands
880 int get_and_validate_layer(const std::wstring& layerstring) {
881 int length = layerstring.length();
882 for (int i = 0; i < length; ++i) {
883 if (!std::isdigit(layerstring[i])) {
884 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
888 return boost::lexical_cast<int>(layerstring);
891 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
893 sink.short_description(L"Prepare a template for displaying.");
894 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
896 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
897 ->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.");
898 sink.para()->text(L"Examples:");
899 sink.example(L"CG 1 ADD 10 svtnews/info 1");
902 std::wstring cg_add_command(command_context& ctx)
904 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
906 int layer = get_and_validate_layer(ctx.parameters.at(0));
907 std::wstring label; //_parameters[2]
908 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
909 unsigned int dataIndex = 3;
911 if (ctx.parameters.at(2).length() > 1)
913 label = ctx.parameters.at(2);
916 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
917 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
920 { //read play-on-load-flag
921 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
924 const wchar_t* pDataString = 0;
925 std::wstring dataFromFile;
926 if (ctx.parameters.size() > dataIndex)
928 const std::wstring& dataString = ctx.parameters.at(dataIndex);
930 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
931 pDataString = dataString.c_str();
934 //The data is not an XML-string, it must be a filename
935 std::wstring filename = env::data_folder();
936 filename.append(dataString);
937 filename.append(L".ftd");
939 auto found_file = find_case_insensitive(filename);
943 dataFromFile = read_file(boost::filesystem::path(*found_file));
944 pDataString = dataFromFile.c_str();
949 auto filename = ctx.parameters.at(1);
950 auto proxy = ctx.cg_registry->get_or_create_proxy(
951 spl::make_shared_ptr(ctx.channel.channel),
952 get_producer_dependencies(ctx.channel.channel, ctx),
953 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
956 if (proxy == core::cg_proxy::empty())
957 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
959 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
961 return L"202 CG OK\r\n";
964 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
966 sink.short_description(L"Play and display a template.");
967 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
968 sink.para()->text(L"Plays and displays the template in the specified layer.");
969 sink.para()->text(L"Examples:");
970 sink.example(L"CG 1 PLAY 0");
973 std::wstring cg_play_command(command_context& ctx)
975 int layer = get_and_validate_layer(ctx.parameters.at(0));
976 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
978 return L"202 CG OK\r\n";
981 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
983 sink.short_description(L"Stop and remove a template.");
984 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
986 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
987 ->text(L" in that the template gets a chance to animate out when it is stopped.");
988 sink.para()->text(L"Examples:");
989 sink.example(L"CG 1 STOP 0");
992 std::wstring cg_stop_command(command_context& ctx)
994 int layer = get_and_validate_layer(ctx.parameters.at(0));
995 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->stop(layer, 0);
997 return L"202 CG OK\r\n";
1000 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1002 sink.short_description(LR"(Trigger a "continue" in a template.)");
1003 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1005 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1006 ->text(L"This is used to control animations that has multiple discreet steps.");
1007 sink.para()->text(L"Examples:");
1008 sink.example(L"CG 1 NEXT 0");
1011 std::wstring cg_next_command(command_context& ctx)
1013 int layer = get_and_validate_layer(ctx.parameters.at(0));
1014 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->next(layer);
1016 return L"202 CG OK\r\n";
1019 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1021 sink.short_description(L"Remove a template.");
1022 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1023 sink.para()->text(L"Removes the template from the specified layer.");
1024 sink.para()->text(L"Examples:");
1025 sink.example(L"CG 1 REMOVE 0");
1028 std::wstring cg_remove_command(command_context& ctx)
1030 int layer = get_and_validate_layer(ctx.parameters.at(0));
1031 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->remove(layer);
1033 return L"202 CG OK\r\n";
1036 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1038 sink.short_description(L"Remove all templates on a video layer.");
1039 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1040 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1041 sink.para()->text(L"Examples:");
1042 sink.example(L"CG 1 CLEAR");
1045 std::wstring cg_clear_command(command_context& ctx)
1047 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1049 return L"202 CG OK\r\n";
1052 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1054 sink.short_description(L"Update a template with new data.");
1055 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1056 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.");
1059 std::wstring cg_update_command(command_context& ctx)
1061 int layer = get_and_validate_layer(ctx.parameters.at(0));
1063 std::wstring dataString = ctx.parameters.at(1);
1064 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1066 //The data is not XML or Json, it must be a filename
1067 std::wstring filename = env::data_folder();
1068 filename.append(dataString);
1069 filename.append(L".ftd");
1071 dataString = read_file(boost::filesystem::path(filename));
1074 ctx.cg_registry->get_proxy(
1075 spl::make_shared_ptr(ctx.channel.channel),
1076 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1077 ->update(layer, dataString);
1079 return L"202 CG OK\r\n";
1082 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1084 sink.short_description(L"Invoke a method/label on a template.");
1085 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1086 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1087 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1090 std::wstring cg_invoke_command(command_context& ctx)
1092 std::wstringstream replyString;
1093 replyString << L"201 CG OK\r\n";
1094 int layer = get_and_validate_layer(ctx.parameters.at(0));
1095 auto result = ctx.cg_registry->get_proxy(
1096 spl::make_shared_ptr(ctx.channel.channel),
1097 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1098 ->invoke(layer, ctx.parameters.at(1));
1099 replyString << result << L"\r\n";
1101 return replyString.str();
1104 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1106 sink.short_description(L"Get information about a running template or the template host.");
1107 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1108 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1109 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1112 std::wstring cg_info_command(command_context& ctx)
1114 std::wstringstream replyString;
1115 replyString << L"201 CG OK\r\n";
1117 if (ctx.parameters.empty())
1119 auto info = ctx.cg_registry->get_proxy(
1120 spl::make_shared_ptr(ctx.channel.channel),
1121 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1122 ->template_host_info();
1123 replyString << info << L"\r\n";
1127 int layer = get_and_validate_layer(ctx.parameters.at(0));
1128 auto desc = ctx.cg_registry->get_proxy(
1129 spl::make_shared_ptr(ctx.channel.channel),
1130 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))
1131 ->description(layer);
1133 replyString << desc << L"\r\n";
1136 return replyString.str();
1141 core::frame_transform get_current_transform(command_context& ctx)
1143 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1146 template<typename Func>
1147 std::wstring reply_value(command_context& ctx, const Func& extractor)
1149 auto value = extractor(get_current_transform(ctx));
1151 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1154 class transforms_applier
1156 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1158 std::vector<stage::transform_tuple_t> transforms_;
1159 command_context& ctx_;
1162 transforms_applier(command_context& ctx)
1165 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1168 ctx.parameters.pop_back();
1171 void add(stage::transform_tuple_t&& transform)
1173 transforms_.push_back(std::move(transform));
1176 void commit_deferred()
1178 ctx_.channel.channel->stage().apply_transforms(
1179 std::move(deferred_transforms_[ctx_.channel_index]));
1186 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1187 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1190 ctx_.channel.channel->stage().apply_transforms(transforms_);
1193 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1195 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1197 sink.short_description(L"Let a layer act as alpha for the one obove.");
1198 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1200 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1201 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1202 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1203 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1204 ->text(L"instead it will be used as the key for the layer above.");
1205 sink.para()->text(L"Examples:");
1206 sink.example(L">> MIXER 1-0 KEYER 1");
1208 L">> MIXER 1-0 KEYER\n"
1209 L"<< 201 MIXER OK\n"
1210 L"<< 1", L"to retrieve the current state");
1213 std::wstring mixer_keyer_command(command_context& ctx)
1215 if (ctx.parameters.empty())
1216 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1218 transforms_applier transforms(ctx);
1219 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1220 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1222 transform.image_transform.is_key = value;
1227 return L"202 MIXER OK\r\n";
1230 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1232 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1234 sink.short_description(L"Enable chroma keying on a layer.");
1235 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1237 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1238 sink.para()->text(L"Examples:");
1239 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1240 sink.example(L">> MIXER 1-1 CHROMA none");
1242 L">> MIXER 1-1 BLEND\n"
1243 L"<< 201 MIXER OK\n"
1244 L"<< SCREEN", L"for getting the current blend mode");
1247 std::wstring mixer_chroma_command(command_context& ctx)
1249 if (ctx.parameters.empty())
1251 auto chroma = get_current_transform(ctx).image_transform.chroma;
1252 return L"201 MIXER OK\r\n"
1253 + core::get_chroma_mode(chroma.key) + L" "
1254 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1255 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1256 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1259 transforms_applier transforms(ctx);
1260 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1261 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1263 core::chroma chroma;
1264 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1266 if (chroma.key != core::chroma::type::none)
1268 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1269 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1270 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1273 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1275 transform.image_transform.chroma = chroma;
1277 }, duration, tween));
1280 return L"202 MIXER OK\r\n";
1283 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1285 sink.short_description(L"Set the blend mode for a layer.");
1286 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1288 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1289 ->text(L"If no argument is given the current blend mode is returned.");
1291 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1292 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1293 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1294 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1295 sink.para()->text(L"Examples:");
1296 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1298 L">> MIXER 1-1 BLEND\n"
1299 L"<< 201 MIXER OK\n"
1300 L"<< SCREEN", L"for getting the current blend mode");
1301 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1304 std::wstring mixer_blend_command(command_context& ctx)
1306 if (ctx.parameters.empty())
1307 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1309 transforms_applier transforms(ctx);
1310 auto value = get_blend_mode(ctx.parameters.at(0));
1311 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1313 transform.image_transform.blend_mode = value;
1318 return L"202 MIXER OK\r\n";
1321 template<typename Getter, typename Setter>
1322 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1324 if (ctx.parameters.empty())
1325 return reply_value(ctx, getter);
1327 transforms_applier transforms(ctx);
1328 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1329 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1330 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1332 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1334 setter(transform, value);
1336 }, duration, tween));
1339 return L"202 MIXER OK\r\n";
1342 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1344 sink.short_description(L"Change the opacity of a layer.");
1345 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1346 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1347 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1348 sink.para()->text(L"Examples:");
1349 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1351 L">> MIXER 1-0 OPACITY\n"
1352 L"<< 201 MIXER OK\n"
1353 L"<< 0.5", L"to retrieve the current opacity");
1356 std::wstring mixer_opacity_command(command_context& ctx)
1358 return single_double_animatable_mixer_command(
1360 [](const frame_transform& t) { return t.image_transform.opacity; },
1361 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1364 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1366 sink.short_description(L"Change the brightness of a layer.");
1367 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1368 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1369 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1370 sink.para()->text(L"Examples:");
1371 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1373 L">> MIXER 1-0 BRIGHTNESS\n"
1374 L"<< 201 MIXER OK\n"
1375 L"0.5", L"to retrieve the current brightness");
1378 std::wstring mixer_brightness_command(command_context& ctx)
1380 return single_double_animatable_mixer_command(
1382 [](const frame_transform& t) { return t.image_transform.brightness; },
1383 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1386 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1388 sink.short_description(L"Change the saturation of a layer.");
1389 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1390 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1391 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1392 sink.para()->text(L"Examples:");
1393 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1395 L">> MIXER 1-0 SATURATION\n"
1396 L"<< 201 MIXER OK\n"
1397 L"<< 0.5", L"to retrieve the current saturation");
1400 std::wstring mixer_saturation_command(command_context& ctx)
1402 return single_double_animatable_mixer_command(
1404 [](const frame_transform& t) { return t.image_transform.saturation; },
1405 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1408 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1410 sink.short_description(L"Change the contrast of a layer.");
1411 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1412 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1413 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1414 sink.para()->text(L"Examples:");
1415 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1417 L">> MIXER 1-0 CONTRAST\n"
1418 L"<< 201 MIXER OK\n"
1419 L"<< 0.5", L"to retrieve the current contrast");
1422 std::wstring mixer_contrast_command(command_context& ctx)
1424 return single_double_animatable_mixer_command(
1426 [](const frame_transform& t) { return t.image_transform.contrast; },
1427 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1430 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1432 sink.short_description(L"Adjust the video levels of a layer.");
1433 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);
1435 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1437 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1438 ->item(L"gamma", L"Adjusts the gamma of the image.")
1439 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1440 sink.para()->text(L"Examples:");
1441 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");
1442 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");
1443 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1445 L">> MIXER 1-10 LEVELS\n"
1446 L"<< 201 MIXER OK\n"
1447 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1450 std::wstring mixer_levels_command(command_context& ctx)
1452 if (ctx.parameters.empty())
1454 auto levels = get_current_transform(ctx).image_transform.levels;
1455 return L"201 MIXER OK\r\n"
1456 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1457 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1458 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1459 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1460 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1463 transforms_applier transforms(ctx);
1465 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1466 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1467 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1468 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1469 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1470 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1471 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1473 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1475 transform.image_transform.levels = value;
1477 }, duration, tween));
1480 return L"202 MIXER OK\r\n";
1483 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1485 sink.short_description(L"Change the fill position and scale of a layer.");
1486 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1488 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1489 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1490 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1491 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1493 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1494 ->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.");
1495 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1497 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1498 ->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, ")
1499 ->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");
1501 ->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.")
1502 ->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.")
1503 ->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.")
1504 ->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.");
1505 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1506 sink.para()->text(L"Examples:");
1507 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1509 L">> MIXER 1-0 FILL\n"
1510 L"<< 201 MIXER OK\n"
1511 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1514 std::wstring mixer_fill_command(command_context& ctx)
1516 if (ctx.parameters.empty())
1518 auto transform = get_current_transform(ctx).image_transform;
1519 auto translation = transform.fill_translation;
1520 auto scale = transform.fill_scale;
1521 return L"201 MIXER OK\r\n"
1522 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1523 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1524 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1525 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1528 transforms_applier transforms(ctx);
1529 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1530 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1531 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1532 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1533 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1534 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1536 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1538 transform.image_transform.fill_translation[0] = x;
1539 transform.image_transform.fill_translation[1] = y;
1540 transform.image_transform.fill_scale[0] = x_s;
1541 transform.image_transform.fill_scale[1] = y_s;
1543 }, duration, tween));
1546 return L"202 MIXER OK\r\n";
1549 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1551 sink.short_description(L"Change the clipping viewport of a layer.");
1552 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1554 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1555 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1556 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1558 ->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.")
1559 ->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.")
1560 ->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.")
1561 ->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.");
1562 sink.para()->text(L"Examples:");
1563 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1565 L">> MIXER 1-0 CLIP\n"
1566 L"<< 201 MIXER OK\n"
1567 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1570 std::wstring mixer_clip_command(command_context& ctx)
1572 if (ctx.parameters.empty())
1574 auto transform = get_current_transform(ctx).image_transform;
1575 auto translation = transform.clip_translation;
1576 auto scale = transform.clip_scale;
1578 return L"201 MIXER OK\r\n"
1579 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1580 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1581 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1582 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1585 transforms_applier transforms(ctx);
1586 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1587 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1588 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1589 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1590 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1591 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1593 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1595 transform.image_transform.clip_translation[0] = x;
1596 transform.image_transform.clip_translation[1] = y;
1597 transform.image_transform.clip_scale[0] = x_s;
1598 transform.image_transform.clip_scale[1] = y_s;
1600 }, duration, tween));
1603 return L"202 MIXER OK\r\n";
1606 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1608 sink.short_description(L"Change the anchor point of a layer.");
1609 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1610 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1612 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1613 ->text(L" will be done from.");
1615 ->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.")
1616 ->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.");
1617 sink.para()->text(L"Examples:");
1618 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1620 L">> MIXER 1-10 ANCHOR\n"
1621 L"<< 201 MIXER OK\n"
1622 L"<< 0.5 0.6", L"gets the anchor point");
1625 std::wstring mixer_anchor_command(command_context& ctx)
1627 if (ctx.parameters.empty())
1629 auto transform = get_current_transform(ctx).image_transform;
1630 auto anchor = transform.anchor;
1631 return L"201 MIXER OK\r\n"
1632 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1633 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1636 transforms_applier transforms(ctx);
1637 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1638 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1639 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1640 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1642 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1644 transform.image_transform.anchor[0] = x;
1645 transform.image_transform.anchor[1] = y;
1647 }, duration, tween));
1650 return L"202 MIXER OK\r\n";
1653 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1655 sink.short_description(L"Crop a layer.");
1656 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);
1658 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1659 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1660 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1662 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1663 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1664 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1665 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1666 sink.para()->text(L"Examples:");
1667 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");
1669 L">> MIXER 1-0 CROP\n"
1670 L"<< 201 MIXER OK\n"
1671 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1674 std::wstring mixer_crop_command(command_context& ctx)
1676 if (ctx.parameters.empty())
1678 auto crop = get_current_transform(ctx).image_transform.crop;
1679 return L"201 MIXER OK\r\n"
1680 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1681 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1682 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1683 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1686 transforms_applier transforms(ctx);
1687 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1688 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1689 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1690 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1691 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1692 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1694 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1696 transform.image_transform.crop.ul[0] = ul_x;
1697 transform.image_transform.crop.ul[1] = ul_y;
1698 transform.image_transform.crop.lr[0] = lr_x;
1699 transform.image_transform.crop.lr[1] = lr_y;
1701 }, duration, tween));
1704 return L"202 MIXER OK\r\n";
1707 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1709 sink.short_description(L"Rotate a layer.");
1710 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1712 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1713 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1714 sink.para()->text(L"Examples:");
1715 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1717 L">> MIXER 1-0 ROTATION\n"
1718 L"<< 201 MIXER OK\n"
1719 L"<< 45", L"to retrieve the current angle");
1722 std::wstring mixer_rotation_command(command_context& ctx)
1724 static const double PI = 3.141592653589793;
1726 return single_double_animatable_mixer_command(
1728 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1729 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1732 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1734 sink.short_description(L"Adjust the perspective transform of a layer.");
1735 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);
1737 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1739 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1740 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1741 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1742 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1743 sink.para()->text(L"Examples:");
1744 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1746 L">> MIXER 1-10 PERSPECTIVE\n"
1747 L"<< 201 MIXER OK\n"
1748 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1751 std::wstring mixer_perspective_command(command_context& ctx)
1753 if (ctx.parameters.empty())
1755 auto perspective = get_current_transform(ctx).image_transform.perspective;
1758 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1759 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1760 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1761 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1762 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1763 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1764 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1765 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1768 transforms_applier transforms(ctx);
1769 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1770 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1771 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1772 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1773 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1774 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1775 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1776 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1777 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1778 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1780 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1782 transform.image_transform.perspective.ul[0] = ul_x;
1783 transform.image_transform.perspective.ul[1] = ul_y;
1784 transform.image_transform.perspective.ur[0] = ur_x;
1785 transform.image_transform.perspective.ur[1] = ur_y;
1786 transform.image_transform.perspective.lr[0] = lr_x;
1787 transform.image_transform.perspective.lr[1] = lr_y;
1788 transform.image_transform.perspective.ll[0] = ll_x;
1789 transform.image_transform.perspective.ll[1] = ll_y;
1791 }, duration, tween));
1794 return L"202 MIXER OK\r\n";
1797 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1799 sink.short_description(L"Enable or disable mipmapping for a layer.");
1800 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1802 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1803 ->text(L"If no argument is given the current state is returned.");
1804 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1805 sink.para()->text(L"Examples:");
1806 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1808 L">> MIXER 1-10 MIPMAP\n"
1809 L"<< 201 MIXER OK\n"
1810 L"<< 1", L"for getting the current state");
1813 std::wstring mixer_mipmap_command(command_context& ctx)
1815 if (ctx.parameters.empty())
1816 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1818 transforms_applier transforms(ctx);
1819 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1820 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1822 transform.image_transform.use_mipmap = value;
1827 return L"202 MIXER OK\r\n";
1830 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1832 sink.short_description(L"Change the volume of a layer.");
1833 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1834 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1835 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1836 sink.para()->text(L"Examples:");
1837 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1838 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1840 L">> MIXER 1-0 VOLUME\n"
1841 L"<< 201 MIXER OK\n"
1842 L"<< 0.8", L"to retrieve the current volume");
1845 std::wstring mixer_volume_command(command_context& ctx)
1847 return single_double_animatable_mixer_command(
1849 [](const frame_transform& t) { return t.audio_transform.volume; },
1850 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1853 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1855 sink.short_description(L"Change the volume of an entire channel.");
1856 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1857 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1858 sink.para()->text(L"Examples:");
1859 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1860 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1861 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1864 std::wstring mixer_mastervolume_command(command_context& ctx)
1866 if (ctx.parameters.empty())
1868 auto volume = ctx.channel.channel->mixer().get_master_volume();
1869 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1872 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1873 ctx.channel.channel->mixer().set_master_volume(master_volume);
1875 return L"202 MIXER OK\r\n";
1878 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1880 sink.short_description(L"Create a grid of video layers.");
1881 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1883 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1884 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1885 sink.para()->text(L"Examples:");
1886 sink.example(L">> MIXER 1 GRID 2");
1889 std::wstring mixer_grid_command(command_context& ctx)
1891 transforms_applier transforms(ctx);
1892 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1893 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1894 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1895 double delta = 1.0 / static_cast<double>(n);
1896 for (int x = 0; x < n; ++x)
1898 for (int y = 0; y < n; ++y)
1900 int index = x + y*n + 1;
1901 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1903 transform.image_transform.fill_translation[0] = x*delta;
1904 transform.image_transform.fill_translation[1] = y*delta;
1905 transform.image_transform.fill_scale[0] = delta;
1906 transform.image_transform.fill_scale[1] = delta;
1907 transform.image_transform.clip_translation[0] = x*delta;
1908 transform.image_transform.clip_translation[1] = y*delta;
1909 transform.image_transform.clip_scale[0] = delta;
1910 transform.image_transform.clip_scale[1] = delta;
1912 }, duration, tween));
1917 return L"202 MIXER OK\r\n";
1920 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1922 sink.short_description(L"Commit all deferred mixer transforms.");
1923 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} COMMIT");
1924 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1925 sink.para()->text(L"Examples:");
1927 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1928 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1929 L">> MIXER 1 COMMIT");
1932 std::wstring mixer_commit_command(command_context& ctx)
1934 transforms_applier transforms(ctx);
1935 transforms.commit_deferred();
1937 return L"202 MIXER OK\r\n";
1940 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1942 sink.short_description(L"Clear all transformations on a channel or layer.");
1943 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1944 sink.para()->text(L"Clears all transformations on a channel or layer.");
1945 sink.para()->text(L"Examples:");
1946 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1947 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1950 std::wstring mixer_clear_command(command_context& ctx)
1952 int layer = ctx.layer_id;
1955 ctx.channel.channel->stage().clear_transforms();
1957 ctx.channel.channel->stage().clear_transforms(layer);
1959 return L"202 MIXER OK\r\n";
1962 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1964 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
1965 sink.syntax(L"CHANNEL_GRID");
1966 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
1968 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
1969 ->code(L"casparcg.config")->text(L" for this to work correctly.");
1972 std::wstring channel_grid_command(command_context& ctx)
1975 auto self = ctx.channels.back();
1977 core::diagnostics::scoped_call_context save;
1978 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
1980 std::vector<std::wstring> params;
1981 params.push_back(L"SCREEN");
1982 params.push_back(L"0");
1983 params.push_back(L"NAME");
1984 params.push_back(L"Channel Grid Window");
1985 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
1987 self.channel->output().add(screen);
1989 for (auto& channel : ctx.channels)
1991 if (channel.channel != self.channel)
1993 core::diagnostics::call_context::for_thread().layer = index;
1994 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
1995 self.channel->stage().load(index, producer, false);
1996 self.channel->stage().play(index);
2001 auto num_channels = ctx.channels.size() - 1;
2002 int square_side_length = std::ceil(std::sqrt(num_channels));
2004 ctx.channel_index = self.channel->index();
2006 ctx.parameters.clear();
2007 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2008 mixer_grid_command(ctx);
2010 return L"202 CHANNEL_GRID OK\r\n";
2013 // Thumbnail Commands
2015 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2017 sink.short_description(L"List all thumbnails.");
2018 sink.syntax(L"THUMBNAIL LIST");
2019 sink.para()->text(L"Lists all thumbnails.");
2020 sink.para()->text(L"Examples:");
2022 L">> THUMBNAIL LIST\n"
2023 L"<< 200 THUMBNAIL LIST OK\n"
2024 L"<< \"AMB\" 20130301T124409 1149\n"
2025 L"<< \"foo/bar\" 20130523T234001 244");
2028 std::wstring thumbnail_list_command(command_context& ctx)
2030 std::wstringstream replyString;
2031 replyString << L"200 THUMBNAIL LIST OK\r\n";
2033 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2035 if (boost::filesystem::is_regular_file(itr->path()))
2037 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2040 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
2042 auto str = relativePath.replace_extension(L"").generic_wstring();
2043 if (str[0] == '\\' || str[0] == '/')
2044 str = std::wstring(str.begin() + 1, str.end());
2046 auto mtime = boost::filesystem::last_write_time(itr->path());
2047 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2048 auto file_size = boost::filesystem::file_size(itr->path());
2050 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2054 replyString << L"\r\n";
2056 return boost::to_upper_copy(replyString.str());
2059 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2061 sink.short_description(L"Retrieve a thumbnail.");
2062 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2063 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2064 sink.para()->text(L"Examples:");
2066 L">> THUMBNAIL RETRIEVE foo/bar\n"
2067 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2068 L"<< ...base64 data...");
2071 std::wstring thumbnail_retrieve_command(command_context& ctx)
2073 std::wstring filename = env::thumbnails_folder();
2074 filename.append(ctx.parameters.at(0));
2075 filename.append(L".png");
2077 std::wstring file_contents;
2079 auto found_file = find_case_insensitive(filename);
2082 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2084 if (file_contents.empty())
2085 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2087 std::wstringstream reply;
2089 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2090 reply << file_contents;
2095 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2097 sink.short_description(L"Regenerate a thumbnail.");
2098 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2099 sink.para()->text(L"Regenerates a thumbnail.");
2102 std::wstring thumbnail_generate_command(command_context& ctx)
2106 ctx.thumb_gen->generate(ctx.parameters.at(0));
2107 return L"202 THUMBNAIL GENERATE OK\r\n";
2110 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2113 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2115 sink.short_description(L"Regenerate all thumbnails.");
2116 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2117 sink.para()->text(L"Regenerates all thumbnails.");
2120 std::wstring thumbnail_generateall_command(command_context& ctx)
2124 ctx.thumb_gen->generate_all();
2125 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2128 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2133 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2135 sink.short_description(L"Get information about a media file.");
2136 sink.syntax(L"CINF [filename:string]");
2137 sink.para()->text(L"Returns information about a media file.");
2140 std::wstring cinf_command(command_context& ctx)
2143 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2145 auto path = itr->path();
2146 auto file = path.replace_extension(L"").filename().wstring();
2147 if (boost::iequals(file, ctx.parameters.at(0)))
2148 info += MediaInfo(itr->path(), ctx.media_info_repo) + L"\r\n";
2152 CASPAR_THROW_EXCEPTION(file_not_found());
2154 std::wstringstream replyString;
2155 replyString << L"200 CINF OK\r\n";
2156 replyString << info << "\r\n";
2158 return replyString.str();
2161 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2163 sink.short_description(L"List all media files.");
2164 sink.syntax(L"CLS");
2166 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2167 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2170 std::wstring cls_command(command_context& ctx)
2172 std::wstringstream replyString;
2173 replyString << L"200 CLS OK\r\n";
2174 replyString << ListMedia(ctx.media_info_repo);
2175 replyString << L"\r\n";
2176 return boost::to_upper_copy(replyString.str());
2179 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2181 sink.short_description(L"List all templates.");
2182 sink.syntax(L"TLS");
2184 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2185 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2188 std::wstring tls_command(command_context& ctx)
2190 std::wstringstream replyString;
2191 replyString << L"200 TLS OK\r\n";
2193 replyString << ListTemplates(ctx.cg_registry);
2194 replyString << L"\r\n";
2196 return replyString.str();
2199 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2201 sink.short_description(L"Get version information.");
2202 sink.syntax(L"VERSION {[component:string]}");
2203 sink.para()->text(L"Returns the version of specified component.");
2204 sink.para()->text(L"Examples:");
2207 L"<< 201 VERSION OK\n"
2208 L"<< 2.1.0.f207a33 STABLE");
2210 L">> VERSION SERVER\n"
2211 L"<< 201 VERSION OK\n"
2212 L"<< 2.1.0.f207a33 STABLE");
2214 L">> VERSION FLASH\n"
2215 L"<< 201 VERSION OK\n"
2219 std::wstring version_command(command_context& ctx)
2221 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2223 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2225 return L"201 VERSION OK\r\n" + version + L"\r\n";
2228 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2231 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2233 sink.short_description(L"Get a list of the available channels.");
2234 sink.syntax(L"INFO");
2235 sink.para()->text(L"Retrieves a list of the available channels.");
2239 L"<< 1 720p5000 PLAYING\n"
2240 L"<< 2 PAL PLAYING");
2243 std::wstring info_command(command_context& ctx)
2245 std::wstringstream replyString;
2246 // This is needed for backwards compatibility with old clients
2247 replyString << L"200 INFO OK\r\n";
2248 for (size_t n = 0; n < ctx.channels.size(); ++n)
2249 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2250 replyString << L"\r\n";
2251 return replyString.str();
2254 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2256 std::wstringstream replyString;
2258 if (command.empty())
2259 replyString << L"201 INFO OK\r\n";
2261 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2263 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2264 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2265 replyString << L"\r\n";
2266 return replyString.str();
2269 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2271 sink.short_description(L"Get information about a channel or a layer.");
2272 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2273 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2274 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2277 std::wstring info_channel_command(command_context& ctx)
2279 boost::property_tree::wptree info;
2280 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2282 if (layer == std::numeric_limits<int>::min())
2284 info.add_child(L"channel", ctx.channel.channel->info())
2285 .add(L"index", ctx.channel_index);
2289 if (ctx.parameters.size() >= 1)
2291 if (boost::iequals(ctx.parameters.at(0), L"B"))
2292 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2294 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2298 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2302 return create_info_xml_reply(info);
2305 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2307 sink.short_description(L"Get information about a template.");
2308 sink.syntax(L"INFO TEMPLATE [template:string]");
2309 sink.para()->text(L"Gets information about the specified template.");
2312 std::wstring info_template_command(command_context& ctx)
2314 auto filename = ctx.parameters.at(0);
2316 std::wstringstream str;
2317 str << u16(ctx.cg_registry->read_meta_info(filename));
2318 boost::property_tree::wptree info;
2319 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2321 return create_info_xml_reply(info, L"TEMPLATE");
2324 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2326 sink.short_description(L"Get the contents of the configuration used.");
2327 sink.syntax(L"INFO CONFIG");
2328 sink.para()->text(L"Gets the contents of the configuration used.");
2331 std::wstring info_config_command(command_context& ctx)
2333 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2336 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2338 sink.short_description(L"Get information about the paths used.");
2339 sink.syntax(L"INFO PATHS");
2340 sink.para()->text(L"Gets information about the paths used.");
2343 std::wstring info_paths_command(command_context& ctx)
2345 boost::property_tree::wptree info;
2346 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2347 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2349 return create_info_xml_reply(info, L"PATHS");
2352 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2354 sink.short_description(L"Get system information.");
2355 sink.syntax(L"INFO SYSTEM");
2356 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2359 std::wstring info_system_command(command_context& ctx)
2361 boost::property_tree::wptree info;
2363 info.add(L"system.name", caspar::system_product_name());
2364 info.add(L"system.os.description", caspar::os_description());
2365 info.add(L"system.cpu", caspar::cpu_info());
2367 ctx.system_info_repo->fill_information(info);
2369 return create_info_xml_reply(info, L"SYSTEM");
2372 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2374 sink.short_description(L"Get detailed information about all channels.");
2375 sink.syntax(L"INFO SERVER");
2376 sink.para()->text(L"Gets detailed information about all channels.");
2379 std::wstring info_server_command(command_context& ctx)
2381 boost::property_tree::wptree info;
2384 for (auto& channel : ctx.channels)
2385 info.add_child(L"channels.channel", channel.channel->info())
2386 .add(L"index", ++index);
2388 return create_info_xml_reply(info, L"SERVER");
2391 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2393 sink.short_description(L"Open the diagnostics window.");
2394 sink.syntax(L"DIAG");
2395 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2398 std::wstring diag_command(command_context& ctx)
2400 core::diagnostics::osd::show_graphs(true);
2402 return L"202 DIAG OK\r\n";
2405 static const int WIDTH = 80;
2407 struct max_width_sink : public core::help_sink
2409 std::size_t max_width = 0;
2411 void begin_item(const std::wstring& name) override
2413 max_width = std::max(name.length(), max_width);
2417 struct short_description_sink : public core::help_sink
2420 std::wstringstream& out;
2422 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2424 void begin_item(const std::wstring& name) override
2426 out << std::left << std::setw(width + 1) << name;
2429 void short_description(const std::wstring& short_description) override
2431 out << short_description << L"\r\n";
2435 struct simple_paragraph_builder : core::paragraph_builder
2437 std::wostringstream out;
2438 std::wstringstream& commit_to;
2440 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2441 ~simple_paragraph_builder()
2443 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2445 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2447 out << std::move(text);
2448 return shared_from_this();
2450 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2451 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2452 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2455 struct simple_definition_list_builder : core::definition_list_builder
2457 std::wstringstream& out;
2459 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2460 ~simple_definition_list_builder()
2465 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2467 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2468 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2469 return shared_from_this();
2473 struct long_description_sink : public core::help_sink
2475 std::wstringstream& out;
2477 long_description_sink(std::wstringstream& out) : out(out) { }
2479 void syntax(const std::wstring& syntax) override
2481 out << L"Syntax:\n";
2482 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2485 spl::shared_ptr<core::paragraph_builder> para() override
2487 return spl::make_shared<simple_paragraph_builder>(out);
2490 spl::shared_ptr<core::definition_list_builder> definitions() override
2492 return spl::make_shared<simple_definition_list_builder>(out);
2495 void example(const std::wstring& code, const std::wstring& caption) override
2497 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2499 if (!caption.empty())
2500 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2505 void begin_item(const std::wstring& name) override
2507 out << name << L"\n\n";
2511 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2513 std::wstringstream result;
2514 result << L"200 " << help_command << L" OK\r\n";
2515 max_width_sink width;
2516 ctx.help_repo->help(tags, width);
2517 short_description_sink sink(width.max_width, result);
2518 sink.width = width.max_width;
2519 ctx.help_repo->help(tags, sink);
2521 return result.str();
2524 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2526 std::wstringstream result;
2527 result << L"201 " << help_command << L" OK\r\n";
2528 auto joined = boost::join(ctx.parameters, L" ");
2529 long_description_sink sink(result);
2530 ctx.help_repo->help(tags, joined, sink);
2532 return result.str();
2535 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2537 sink.short_description(L"Show online help for AMCP commands.");
2538 sink.syntax(LR"(HELP {[command:string]})");
2539 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2540 sink.example(L">> HELP", L"Shows a list of commands.");
2541 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2544 std::wstring help_command(command_context& ctx)
2546 if (ctx.parameters.size() == 0)
2547 return create_help_list(L"HELP", ctx, { L"AMCP" });
2549 return create_help_details(L"HELP", ctx, { L"AMCP" });
2552 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2554 sink.short_description(L"Show online help for producers.");
2555 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2556 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2557 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2558 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2561 std::wstring help_producer_command(command_context& ctx)
2563 if (ctx.parameters.size() == 0)
2564 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2566 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2569 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2571 sink.short_description(L"Disconnect the session.");
2572 sink.syntax(L"BYE");
2574 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2575 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2578 std::wstring bye_command(command_context& ctx)
2580 ctx.client->disconnect();
2584 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2586 sink.short_description(L"Shutdown the server.");
2587 sink.syntax(L"KILL");
2588 sink.para()->text(L"Shuts the server down.");
2591 std::wstring kill_command(command_context& ctx)
2593 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2594 return L"202 KILL OK\r\n";
2597 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2599 sink.short_description(L"Shutdown the server with restart exit code.");
2600 sink.syntax(L"RESTART");
2602 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2603 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2606 std::wstring restart_command(command_context& ctx)
2608 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2609 return L"202 RESTART OK\r\n";
2612 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2614 sink.short_description(L"Lock or unlock access to a channel.");
2615 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2616 sink.para()->text(L"Allows for exclusive access to a channel.");
2617 sink.para()->text(L"Examples:");
2618 sink.example(L"LOCK 1 ACQUIRE secret");
2619 sink.example(L"LOCK 1 RELEASE");
2620 sink.example(L"LOCK 1 CLEAR");
2623 std::wstring lock_command(command_context& ctx)
2625 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2626 auto lock = ctx.channels.at(channel_index).lock;
2627 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2629 if (command == L"ACQUIRE")
2631 std::wstring lock_phrase = ctx.parameters.at(2);
2633 //TODO: read options
2635 //just lock one channel
2636 if (!lock->try_lock(lock_phrase, ctx.client))
2637 return L"503 LOCK ACQUIRE FAILED\r\n";
2639 return L"202 LOCK ACQUIRE OK\r\n";
2641 else if (command == L"RELEASE")
2643 lock->release_lock(ctx.client);
2644 return L"202 LOCK RELEASE OK\r\n";
2646 else if (command == L"CLEAR")
2648 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2649 std::wstring client_override_phrase;
2651 if (!override_phrase.empty())
2652 client_override_phrase = ctx.parameters.at(2);
2654 //just clear one channel
2655 if (client_override_phrase != override_phrase)
2656 return L"503 LOCK CLEAR FAILED\r\n";
2658 lock->clear_locks();
2660 return L"202 LOCK CLEAR OK\r\n";
2663 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2666 void register_commands(amcp_command_repository& repo)
2668 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2669 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2670 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2671 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2672 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2673 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2674 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2675 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2676 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2677 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2678 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2679 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2680 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2681 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2682 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2684 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2685 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2686 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2687 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2689 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2690 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2691 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2692 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2693 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2694 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2695 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2696 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2697 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2699 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2700 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2701 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2702 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2703 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2704 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2705 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2706 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2707 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2708 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2709 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2710 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2711 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2712 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2713 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2714 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2715 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2716 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2717 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2718 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2719 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2721 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2722 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2723 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2724 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2726 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2727 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2728 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2729 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2730 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2731 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2732 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2733 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2734 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2735 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2736 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2737 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2738 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2739 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2740 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2741 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2742 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2746 }} //namespace caspar