2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Nicklas P Andersson
22 #include "../StdAfx.h"
25 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
28 #include "AMCPCommandsImpl.h"
30 #include "amcp_command_repository.h"
31 #include "AMCPCommandQueue.h"
33 #include <common/env.h>
35 #include <common/log.h>
36 #include <common/param.h>
37 #include <common/os/system_info.h>
38 #include <common/os/filesystem.h>
39 #include <common/base64.h>
40 #include <common/thread_info.h>
42 #include <core/producer/cg_proxy.h>
43 #include <core/producer/frame_producer.h>
44 #include <core/help/help_repository.h>
45 #include <core/help/help_sink.h>
46 #include <core/help/util.h>
47 #include <core/video_format.h>
48 #include <core/producer/transition/transition_producer.h>
49 #include <core/frame/audio_channel_layout.h>
50 #include <core/frame/frame_transform.h>
51 #include <core/producer/stage.h>
52 #include <core/producer/layer.h>
53 #include <core/mixer/mixer.h>
54 #include <core/consumer/output.h>
55 #include <core/thumbnail_generator.h>
56 #include <core/producer/media_info/media_info.h>
57 #include <core/producer/media_info/media_info_repository.h>
58 #include <core/diagnostics/call_context.h>
59 #include <core/diagnostics/osd_graph.h>
60 #include <core/system_info_provider.h>
69 #include <boost/date_time/posix_time/posix_time.hpp>
70 #include <boost/lexical_cast.hpp>
71 #include <boost/algorithm/string.hpp>
72 #include <boost/filesystem.hpp>
73 #include <boost/filesystem/fstream.hpp>
74 #include <boost/regex.hpp>
75 #include <boost/property_tree/xml_parser.hpp>
76 #include <boost/locale.hpp>
77 #include <boost/range/adaptor/transformed.hpp>
78 #include <boost/range/algorithm/copy.hpp>
79 #include <boost/archive/iterators/base64_from_binary.hpp>
80 #include <boost/archive/iterators/insert_linebreaks.hpp>
81 #include <boost/archive/iterators/transform_width.hpp>
83 #include <tbb/concurrent_unordered_map.h>
87 102 [action] Information that [action] has happened
88 101 [action] Information that [action] has happened plus one row of data
90 202 [command] OK [command] has been executed
91 201 [command] OK [command] has been executed, plus one row of data
92 200 [command] OK [command] has been executed, plus multiple lines of data. ends with an empty line
94 400 ERROR the command could not be understood
95 401 [command] ERROR invalid/missing channel
96 402 [command] ERROR parameter missing
97 403 [command] ERROR invalid parameter
98 404 [command] ERROR file not found
100 500 FAILED internal error
101 501 [command] FAILED internal error
102 502 [command] FAILED could not read file
103 503 [command] FAILED access denied
105 600 [command] FAILED [command] not implemented
108 namespace caspar { namespace protocol { namespace amcp {
110 using namespace core;
112 std::wstring read_file_base64(const boost::filesystem::path& file)
114 using namespace boost::archive::iterators;
116 boost::filesystem::ifstream filestream(file, std::ios::binary);
121 auto length = boost::filesystem::file_size(file);
122 std::vector<char> bytes;
123 bytes.resize(length);
124 filestream.read(bytes.data(), length);
126 std::string result(to_base64(bytes.data(), length));
127 return std::wstring(result.begin(), result.end());
130 std::wstring read_utf8_file(const boost::filesystem::path& file)
132 std::wstringstream result;
133 boost::filesystem::wifstream filestream(file);
140 result << filestream.rdbuf();
146 std::wstring read_latin1_file(const boost::filesystem::path& file)
148 boost::locale::generator gen;
149 gen.locale_cache_enabled(true);
150 gen.categories(boost::locale::codepage_facet);
152 std::stringstream result_stream;
153 boost::filesystem::ifstream filestream(file);
154 filestream.imbue(gen("en_US.ISO8859-1"));
159 result_stream << filestream.rdbuf();
162 std::string result = result_stream.str();
163 std::wstring widened_result;
165 // The first 255 codepoints in unicode is the same as in latin1
167 result | boost::adaptors::transformed(
168 [](char c) { return static_cast<unsigned char>(c); }),
169 std::back_inserter(widened_result));
171 return widened_result;
174 std::wstring read_file(const boost::filesystem::path& file)
176 static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
178 if (!boost::filesystem::exists(file))
183 if (boost::filesystem::file_size(file) >= 3)
185 boost::filesystem::ifstream bom_stream(file);
188 bom_stream.read(header, 3);
191 if (std::memcmp(BOM, header, 3) == 0)
192 return read_utf8_file(file);
195 return read_latin1_file(file);
198 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
200 if (!boost::filesystem::is_regular_file(path))
203 auto media_info = media_info_repo->get(path.wstring());
208 auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
210 auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
212 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
213 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
214 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
216 auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
217 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
218 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
220 auto str = relativePath.replace_extension(L"").generic_wstring();
221 if (str[0] == '\\' || str[0] == '/')
222 str = std::wstring(str.begin() + 1, str.end());
224 return std::wstring()
226 + L"\" " + media_info->clip_type +
228 + L" " + writeTimeWStr +
229 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
230 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
234 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
236 std::wstringstream replyString;
237 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
238 replyString << MediaInfo(itr->path(), media_info_repo);
240 return boost::to_upper_copy(replyString.str());
243 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
245 std::wstringstream replyString;
247 for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
249 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
251 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
253 auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
254 writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
255 auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
257 auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
258 sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
260 auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
262 auto dir = relativePath.parent_path();
263 auto file = boost::to_upper_copy(relativePath.filename().wstring());
264 relativePath = dir / file;
266 auto str = relativePath.replace_extension(L"").generic_wstring();
267 boost::trim_if(str, boost::is_any_of("\\/"));
269 replyString << L"\"" << str
270 << L"\" " << sizeWStr
271 << L" " << writeTimeWStr
275 return replyString.str();
278 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
280 return core::frame_producer_dependencies(
281 channel->frame_factory(),
282 cpplinq::from(ctx.channels)
283 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
285 channel->video_format_desc(),
286 ctx.producer_registry);
291 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
293 sink.short_description(L"Load a media file or resource in the background.");
294 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]})");
296 ->text(L"Loads a producer in the background and prepares it for playout. ")
297 ->text(L"If no layer is specified the default layer index will be used.");
299 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
300 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
302 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
303 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
305 ->code(L"loop")->text(L" will cause the clip to loop.");
307 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
309 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
311 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
312 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
313 sink.para()->text(L"Examples:");
314 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
315 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
316 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
317 sink.example(L">> LOADBG 1-0 MY_FILE");
319 L">> PLAY 1-1 MY_FILE\n"
320 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
321 L"To automatically fade out a layer after a video file has been played to the end");
323 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
325 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
326 ->code(L"filter")->text(L" command.");
329 std::wstring loadbg_command(command_context& ctx)
331 transition_info transitionInfo;
335 std::wstring message;
336 for (size_t n = 0; n < ctx.parameters.size(); ++n)
337 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
339 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)?.*)");
341 if (boost::regex_match(message, what, expr))
343 auto transition = what["TRANSITION"].str();
344 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
345 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
346 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
347 transitionInfo.tweener = tween;
349 if (transition == L"CUT")
350 transitionInfo.type = transition_type::cut;
351 else if (transition == L"MIX")
352 transitionInfo.type = transition_type::mix;
353 else if (transition == L"PUSH")
354 transitionInfo.type = transition_type::push;
355 else if (transition == L"SLIDE")
356 transitionInfo.type = transition_type::slide;
357 else if (transition == L"WIPE")
358 transitionInfo.type = transition_type::wipe;
360 if (direction == L"FROMLEFT")
361 transitionInfo.direction = transition_direction::from_left;
362 else if (direction == L"FROMRIGHT")
363 transitionInfo.direction = transition_direction::from_right;
364 else if (direction == L"LEFT")
365 transitionInfo.direction = transition_direction::from_right;
366 else if (direction == L"RIGHT")
367 transitionInfo.direction = transition_direction::from_left;
370 //Perform loading of the clip
371 core::diagnostics::scoped_call_context save;
372 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
373 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
375 auto channel = ctx.channel.channel;
376 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
378 if (pFP == frame_producer::empty())
379 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
381 bool auto_play = contains_param(L"AUTO", ctx.parameters);
383 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
385 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
387 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
389 return L"202 LOADBG OK\r\n";
392 void load_describer(core::help_sink& sink, const core::help_repository& repo)
394 sink.short_description(L"Load a media file or resource to the foreground.");
395 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
397 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
398 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
399 sink.para()->text(L"Examples:");
400 sink.example(L">> LOAD 1 MY_FILE");
401 sink.example(L">> LOAD 1-1 MY_FILE");
402 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
405 std::wstring load_command(command_context& ctx)
407 core::diagnostics::scoped_call_context save;
408 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
409 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
410 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
411 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
413 return L"202 LOAD OK\r\n";
416 void play_describer(core::help_sink& sink, const core::help_repository& repository)
418 sink.short_description(L"Play a media file or resource.");
419 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
421 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
422 ->text(L") is prepared, it will be executed.");
424 ->text(L"If additional parameters (see ")->see(L"LOADBG")
425 ->text(L") are provided then the provided clip will first be loaded to the background.");
426 sink.para()->text(L"Examples:");
427 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
428 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
429 sink.example(L">> PLAY 1-0 MY_FILE");
430 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
433 std::wstring play_command(command_context& ctx)
435 if (!ctx.parameters.empty())
438 ctx.channel.channel->stage().play(ctx.layer_index());
440 return L"202 PLAY OK\r\n";
443 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
445 sink.short_description(L"Pause playback of a layer.");
446 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
448 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
449 ->text(L" command can be used to resume playback again.");
450 sink.para()->text(L"Examples:");
451 sink.example(L">> PAUSE 1");
452 sink.example(L">> PAUSE 1-1");
455 std::wstring pause_command(command_context& ctx)
457 ctx.channel.channel->stage().pause(ctx.layer_index());
458 return L"202 PAUSE OK\r\n";
461 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
463 sink.short_description(L"Resume playback of a layer.");
464 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
466 ->text(L"Resumes playback of a foreground clip previously paused with the ")
467 ->see(L"PAUSE")->text(L" command.");
468 sink.para()->text(L"Examples:");
469 sink.example(L">> RESUME 1");
470 sink.example(L">> RESUME 1-1");
473 std::wstring resume_command(command_context& ctx)
475 ctx.channel.channel->stage().resume(ctx.layer_index());
476 return L"202 RESUME OK\r\n";
479 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
481 sink.short_description(L"Remove the foreground clip of a layer.");
482 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
484 ->text(L"Removes the foreground clip of the specified layer.");
485 sink.para()->text(L"Examples:");
486 sink.example(L">> STOP 1");
487 sink.example(L">> STOP 1-1");
490 std::wstring stop_command(command_context& ctx)
492 ctx.channel.channel->stage().stop(ctx.layer_index());
493 return L"202 STOP OK\r\n";
496 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
498 sink.short_description(L"Remove all clips of a layer or an entire channel.");
499 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
501 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
502 ->text(L"If no layer is specified then all layers in the specified ")
503 ->code(L"video_channel")->text(L" are cleared.");
504 sink.para()->text(L"Examples:");
505 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
506 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
509 std::wstring clear_command(command_context& ctx)
511 int index = ctx.layer_index(std::numeric_limits<int>::min());
512 if (index != std::numeric_limits<int>::min())
513 ctx.channel.channel->stage().clear(index);
515 ctx.channel.channel->stage().clear();
517 return L"202 CLEAR OK\r\n";
520 void call_describer(core::help_sink& sink, const core::help_repository& repo)
522 sink.short_description(L"Call a method on a producer.");
523 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
525 ->text(L"Calls method on the specified producer with the provided ")
526 ->code(L"param")->text(L" string.");
527 sink.para()->text(L"Examples:");
528 sink.example(L">> CALL 1 LOOP");
529 sink.example(L">> CALL 1-2 SEEK 25");
532 std::wstring call_command(command_context& ctx)
534 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
536 // TODO: because of std::async deferred timed waiting does not work
538 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
539 if (wait_res == std::future_status::timeout)
540 CASPAR_THROW_EXCEPTION(timed_out());*/
542 std::wstringstream replyString;
543 if (result.get().empty())
544 replyString << L"202 CALL OK\r\n";
546 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
548 return replyString.str();
551 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
553 sink.short_description(L"Swap layers between channels.");
554 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
556 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
557 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
558 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
559 sink.para()->text(L"Examples:");
560 sink.example(L">> SWAP 1 2");
561 sink.example(L">> SWAP 1-1 2-3");
562 sink.example(L">> SWAP 1-1 1-2");
563 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
566 std::wstring swap_command(command_context& ctx)
568 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
570 if (ctx.layer_index(-1) != -1)
572 std::vector<std::string> strs;
573 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
575 auto ch1 = ctx.channel.channel;
576 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
578 int l1 = ctx.layer_index();
579 int l2 = boost::lexical_cast<int>(strs.at(1));
581 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
585 auto ch1 = ctx.channel.channel;
586 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
587 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
590 return L"202 SWAP OK\r\n";
593 void add_describer(core::help_sink& sink, const core::help_repository& repo)
595 sink.short_description(L"Add a consumer to a video channel.");
596 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
598 ->text(L"Adds a consumer to the specified video channel. The string ")
599 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
600 ->text(L"If a successful match is found a consumer will be created and added to the ")
601 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
602 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
603 ->see(L"the CasparCG config file")->text(L".");
604 sink.para()->text(L"Examples:");
605 sink.example(L">> ADD 1 DECKLINK 1");
606 sink.example(L">> ADD 1 BLUEFISH 2");
607 sink.example(L">> ADD 1 SCREEN");
608 sink.example(L">> ADD 1 AUDIO");
609 sink.example(L">> ADD 1 IMAGE filename");
610 sink.example(L">> ADD 1 FILE filename.mov");
611 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
612 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
613 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
616 std::wstring add_command(command_context& ctx)
618 replace_placeholders(
619 L"<CLIENT_IP_ADDRESS>",
620 ctx.client->address(),
623 core::diagnostics::scoped_call_context save;
624 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
626 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
627 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
629 return L"202 ADD OK\r\n";
632 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
634 sink.short_description(L"Remove a consumer from a video channel.");
635 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
637 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
638 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
639 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
640 sink.para()->text(L"Examples:");
641 sink.example(L">> REMOVE 1 DECKLINK 1");
642 sink.example(L">> REMOVE 1 BLUEFISH 2");
643 sink.example(L">> REMOVE 1 SCREEN");
644 sink.example(L">> REMOVE 1 AUDIO");
645 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
648 std::wstring remove_command(command_context& ctx)
650 auto index = ctx.layer_index(std::numeric_limits<int>::min());
652 if (index == std::numeric_limits<int>::min())
654 replace_placeholders(
655 L"<CLIENT_IP_ADDRESS>",
656 ctx.client->address(),
659 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
662 ctx.channel.channel->output().remove(index);
664 return L"202 REMOVE OK\r\n";
667 void print_describer(core::help_sink& sink, const core::help_repository& repo)
669 sink.short_description(L"Take a snapshot of a channel.");
670 sink.syntax(L"PRINT [video_channel:int]");
672 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
673 ->code(L"media")->text(L" folder.");
674 sink.para()->text(L"Examples:");
675 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
678 std::wstring print_command(command_context& ctx)
680 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
682 return L"202 PRINT OK\r\n";
685 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
687 sink.short_description(L"Change the log level of the server.");
688 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
689 sink.para()->text(L"Changes the log level of the server.");
690 sink.para()->text(L"Examples:");
691 sink.example(L">> LOG LEVEL trace");
692 sink.example(L">> LOG LEVEL info");
695 std::wstring log_level_command(command_context& ctx)
697 log::set_log_level(ctx.parameters.at(0));
699 return L"202 LOG OK\r\n";
702 void set_describer(core::help_sink& sink, const core::help_repository& repo)
704 sink.short_description(L"Change the value of a channel variable.");
705 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
706 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
708 ->item(L"MODE", L"Changes the video format of the channel.")
709 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
710 sink.para()->text(L"Examples:");
711 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
712 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
715 std::wstring set_command(command_context& ctx)
717 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
718 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
722 auto format_desc = core::video_format_desc(value);
723 if (format_desc.format != core::video_format::invalid)
725 ctx.channel.channel->video_format_desc(format_desc);
726 return L"202 SET MODE OK\r\n";
729 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
731 else if (name == L"CHANNEL_LAYOUT")
733 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
737 ctx.channel.channel->audio_channel_layout(*channel_layout);
738 return L"202 SET CHANNEL_LAYOUT OK\r\n";
741 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid audio channel layout"));
744 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
747 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
749 sink.short_description(L"Store a dataset.");
750 sink.syntax(L"DATA STORE [name:string] [data:string]");
751 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
752 sink.para()->text(L"Directories will be created if they do not exist.");
753 sink.para()->text(L"Examples:");
754 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
755 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
758 std::wstring data_store_command(command_context& ctx)
760 std::wstring filename = env::data_folder();
761 filename.append(ctx.parameters[0]);
762 filename.append(L".ftd");
764 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
765 auto found_data_path = find_case_insensitive(data_path);
768 data_path = *found_data_path;
770 if (!boost::filesystem::exists(data_path))
771 boost::filesystem::create_directories(data_path);
773 auto found_filename = find_case_insensitive(filename);
776 filename = *found_filename; // Overwrite case insensitive.
778 boost::filesystem::wofstream datafile(filename);
780 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
782 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
783 datafile << ctx.parameters[1] << std::flush;
786 return L"202 DATA STORE OK\r\n";
789 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
791 sink.short_description(L"Retrieve a dataset.");
792 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
793 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
794 sink.para()->text(L"Examples:");
795 sink.example(L">> DATA RETRIEVE my_data");
796 sink.example(L">> DATA RETRIEVE Folder1/my_data");
799 std::wstring data_retrieve_command(command_context& ctx)
801 std::wstring filename = env::data_folder();
802 filename.append(ctx.parameters[0]);
803 filename.append(L".ftd");
805 std::wstring file_contents;
807 auto found_file = find_case_insensitive(filename);
810 file_contents = read_file(boost::filesystem::path(*found_file));
812 if (file_contents.empty())
813 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
815 std::wstringstream reply;
816 reply << L"201 DATA RETRIEVE OK\r\n";
818 std::wstringstream file_contents_stream(file_contents);
821 bool firstLine = true;
822 while (std::getline(file_contents_stream, line))
836 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
838 sink.short_description(L"List stored datasets.");
839 sink.syntax(L"DATA LIST");
840 sink.para()->text(L"Returns a list of all stored datasets.");
843 std::wstring data_list_command(command_context& ctx)
845 std::wstringstream replyString;
846 replyString << L"200 DATA LIST OK\r\n";
848 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
850 if (boost::filesystem::is_regular_file(itr->path()))
852 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
855 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
857 auto str = relativePath.replace_extension(L"").generic_wstring();
858 if (str[0] == L'\\' || str[0] == L'/')
859 str = std::wstring(str.begin() + 1, str.end());
861 replyString << str << L"\r\n";
865 replyString << L"\r\n";
867 return boost::to_upper_copy(replyString.str());
870 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
872 sink.short_description(L"Remove a stored dataset.");
873 sink.syntax(L"DATA REMOVE [name:string]");
874 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
875 sink.para()->text(L"Examples:");
876 sink.example(L">> DATA REMOVE my_data");
877 sink.example(L">> DATA REMOVE Folder1/my_data");
880 std::wstring data_remove_command(command_context& ctx)
882 std::wstring filename = env::data_folder();
883 filename.append(ctx.parameters[0]);
884 filename.append(L".ftd");
886 if (!boost::filesystem::exists(filename))
887 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
889 if (!boost::filesystem::remove(filename))
890 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
892 return L"201 DATA REMOVE OK\r\n";
895 // Template Graphics Commands
897 int get_and_validate_layer(const std::wstring& layerstring) {
898 int length = layerstring.length();
899 for (int i = 0; i < length; ++i) {
900 if (!std::isdigit(layerstring[i])) {
901 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
905 return boost::lexical_cast<int>(layerstring);
908 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
910 sink.short_description(L"Prepare a template for displaying.");
911 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
913 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
914 ->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.");
915 sink.para()->text(L"Examples:");
916 sink.example(L"CG 1 ADD 10 svtnews/info 1");
919 std::wstring cg_add_command(command_context& ctx)
921 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
923 int layer = get_and_validate_layer(ctx.parameters.at(0));
924 std::wstring label; //_parameters[2]
925 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
926 unsigned int dataIndex = 3;
928 if (ctx.parameters.at(2).length() > 1)
930 label = ctx.parameters.at(2);
933 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
934 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
937 { //read play-on-load-flag
938 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
941 const wchar_t* pDataString = 0;
942 std::wstring dataFromFile;
943 if (ctx.parameters.size() > dataIndex)
945 const std::wstring& dataString = ctx.parameters.at(dataIndex);
947 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
948 pDataString = dataString.c_str();
951 //The data is not an XML-string, it must be a filename
952 std::wstring filename = env::data_folder();
953 filename.append(dataString);
954 filename.append(L".ftd");
956 auto found_file = find_case_insensitive(filename);
960 dataFromFile = read_file(boost::filesystem::path(*found_file));
961 pDataString = dataFromFile.c_str();
966 auto filename = ctx.parameters.at(1);
967 auto proxy = ctx.cg_registry->get_or_create_proxy(
968 spl::make_shared_ptr(ctx.channel.channel),
969 get_producer_dependencies(ctx.channel.channel, ctx),
970 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
973 if (proxy == core::cg_proxy::empty())
974 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
976 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
978 return L"202 CG OK\r\n";
981 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
983 sink.short_description(L"Play and display a template.");
984 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
985 sink.para()->text(L"Plays and displays the template in the specified layer.");
986 sink.para()->text(L"Examples:");
987 sink.example(L"CG 1 PLAY 0");
990 std::wstring cg_play_command(command_context& ctx)
992 int layer = get_and_validate_layer(ctx.parameters.at(0));
993 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
995 return L"202 CG OK\r\n";
998 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1000 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1002 if (proxy == cg_proxy::empty())
1003 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No CG proxy running on layer"));
1008 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1010 sink.short_description(L"Stop and remove a template.");
1011 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1013 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1014 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1015 sink.para()->text(L"Examples:");
1016 sink.example(L"CG 1 STOP 0");
1019 std::wstring cg_stop_command(command_context& ctx)
1021 int layer = get_and_validate_layer(ctx.parameters.at(0));
1022 get_expected_cg_proxy(ctx)->stop(layer, 0);
1024 return L"202 CG OK\r\n";
1027 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1029 sink.short_description(LR"(Trigger a "continue" in a template.)");
1030 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1032 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1033 ->text(L"This is used to control animations that has multiple discreet steps.");
1034 sink.para()->text(L"Examples:");
1035 sink.example(L"CG 1 NEXT 0");
1038 std::wstring cg_next_command(command_context& ctx)
1040 int layer = get_and_validate_layer(ctx.parameters.at(0));
1041 get_expected_cg_proxy(ctx)->next(layer);
1043 return L"202 CG OK\r\n";
1046 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1048 sink.short_description(L"Remove a template.");
1049 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1050 sink.para()->text(L"Removes the template from the specified layer.");
1051 sink.para()->text(L"Examples:");
1052 sink.example(L"CG 1 REMOVE 0");
1055 std::wstring cg_remove_command(command_context& ctx)
1057 int layer = get_and_validate_layer(ctx.parameters.at(0));
1058 get_expected_cg_proxy(ctx)->remove(layer);
1060 return L"202 CG OK\r\n";
1063 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1065 sink.short_description(L"Remove all templates on a video layer.");
1066 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1067 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1068 sink.para()->text(L"Examples:");
1069 sink.example(L"CG 1 CLEAR");
1072 std::wstring cg_clear_command(command_context& ctx)
1074 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1076 return L"202 CG OK\r\n";
1079 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1081 sink.short_description(L"Update a template with new data.");
1082 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1083 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.");
1086 std::wstring cg_update_command(command_context& ctx)
1088 int layer = get_and_validate_layer(ctx.parameters.at(0));
1090 std::wstring dataString = ctx.parameters.at(1);
1091 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1093 //The data is not XML or Json, it must be a filename
1094 std::wstring filename = env::data_folder();
1095 filename.append(dataString);
1096 filename.append(L".ftd");
1098 dataString = read_file(boost::filesystem::path(filename));
1101 get_expected_cg_proxy(ctx)->update(layer, dataString);
1103 return L"202 CG OK\r\n";
1106 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1108 sink.short_description(L"Invoke a method/label on a template.");
1109 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1110 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1111 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1114 std::wstring cg_invoke_command(command_context& ctx)
1116 std::wstringstream replyString;
1117 replyString << L"201 CG OK\r\n";
1118 int layer = get_and_validate_layer(ctx.parameters.at(0));
1119 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1120 replyString << result << L"\r\n";
1122 return replyString.str();
1125 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1127 sink.short_description(L"Get information about a running template or the template host.");
1128 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1129 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1130 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1133 std::wstring cg_info_command(command_context& ctx)
1135 std::wstringstream replyString;
1136 replyString << L"201 CG OK\r\n";
1138 if (ctx.parameters.empty())
1140 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1141 replyString << info << L"\r\n";
1145 int layer = get_and_validate_layer(ctx.parameters.at(0));
1146 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1148 replyString << desc << L"\r\n";
1151 return replyString.str();
1156 core::frame_transform get_current_transform(command_context& ctx)
1158 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1161 template<typename Func>
1162 std::wstring reply_value(command_context& ctx, const Func& extractor)
1164 auto value = extractor(get_current_transform(ctx));
1166 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1169 class transforms_applier
1171 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1173 std::vector<stage::transform_tuple_t> transforms_;
1174 command_context& ctx_;
1177 transforms_applier(command_context& ctx)
1180 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1183 ctx.parameters.pop_back();
1186 void add(stage::transform_tuple_t&& transform)
1188 transforms_.push_back(std::move(transform));
1191 void commit_deferred()
1193 auto& transforms = deferred_transforms_[ctx_.channel_index];
1194 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1202 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1203 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1206 ctx_.channel.channel->stage().apply_transforms(transforms_);
1209 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1211 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1213 sink.short_description(L"Let a layer act as alpha for the one obove.");
1214 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1216 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1217 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1218 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1219 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1220 ->text(L"instead it will be used as the key for the layer above.");
1221 sink.para()->text(L"Examples:");
1222 sink.example(L">> MIXER 1-0 KEYER 1");
1224 L">> MIXER 1-0 KEYER\n"
1225 L"<< 201 MIXER OK\n"
1226 L"<< 1", L"to retrieve the current state");
1229 std::wstring mixer_keyer_command(command_context& ctx)
1231 if (ctx.parameters.empty())
1232 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1234 transforms_applier transforms(ctx);
1235 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1236 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1238 transform.image_transform.is_key = value;
1243 return L"202 MIXER OK\r\n";
1246 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1248 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1250 sink.short_description(L"Enable chroma keying on a layer.");
1251 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1253 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1254 sink.para()->text(L"Examples:");
1255 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1256 sink.example(L">> MIXER 1-1 CHROMA none");
1258 L">> MIXER 1-1 BLEND\n"
1259 L"<< 201 MIXER OK\n"
1260 L"<< SCREEN", L"for getting the current blend mode");
1263 std::wstring mixer_chroma_command(command_context& ctx)
1265 if (ctx.parameters.empty())
1267 auto chroma = get_current_transform(ctx).image_transform.chroma;
1268 return L"201 MIXER OK\r\n"
1269 + core::get_chroma_mode(chroma.key) + L" "
1270 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1271 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1272 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1275 transforms_applier transforms(ctx);
1276 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1277 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1279 core::chroma chroma;
1280 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1282 if (chroma.key != core::chroma::type::none)
1284 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1285 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1286 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1289 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1291 transform.image_transform.chroma = chroma;
1293 }, duration, tween));
1296 return L"202 MIXER OK\r\n";
1299 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1301 sink.short_description(L"Set the blend mode for a layer.");
1302 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1304 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1305 ->text(L"If no argument is given the current blend mode is returned.");
1307 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1308 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1309 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1310 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1311 sink.para()->text(L"Examples:");
1312 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1314 L">> MIXER 1-1 BLEND\n"
1315 L"<< 201 MIXER OK\n"
1316 L"<< SCREEN", L"for getting the current blend mode");
1317 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1320 std::wstring mixer_blend_command(command_context& ctx)
1322 if (ctx.parameters.empty())
1323 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1325 transforms_applier transforms(ctx);
1326 auto value = get_blend_mode(ctx.parameters.at(0));
1327 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1329 transform.image_transform.blend_mode = value;
1334 return L"202 MIXER OK\r\n";
1337 template<typename Getter, typename Setter>
1338 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1340 if (ctx.parameters.empty())
1341 return reply_value(ctx, getter);
1343 transforms_applier transforms(ctx);
1344 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1345 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1346 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1348 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1350 setter(transform, value);
1352 }, duration, tween));
1355 return L"202 MIXER OK\r\n";
1358 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1360 sink.short_description(L"Change the opacity of a layer.");
1361 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1362 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1363 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1364 sink.para()->text(L"Examples:");
1365 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1367 L">> MIXER 1-0 OPACITY\n"
1368 L"<< 201 MIXER OK\n"
1369 L"<< 0.5", L"to retrieve the current opacity");
1372 std::wstring mixer_opacity_command(command_context& ctx)
1374 return single_double_animatable_mixer_command(
1376 [](const frame_transform& t) { return t.image_transform.opacity; },
1377 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1380 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1382 sink.short_description(L"Change the brightness of a layer.");
1383 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1384 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1385 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1386 sink.para()->text(L"Examples:");
1387 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1389 L">> MIXER 1-0 BRIGHTNESS\n"
1390 L"<< 201 MIXER OK\n"
1391 L"0.5", L"to retrieve the current brightness");
1394 std::wstring mixer_brightness_command(command_context& ctx)
1396 return single_double_animatable_mixer_command(
1398 [](const frame_transform& t) { return t.image_transform.brightness; },
1399 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1402 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1404 sink.short_description(L"Change the saturation of a layer.");
1405 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1406 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1407 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1408 sink.para()->text(L"Examples:");
1409 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1411 L">> MIXER 1-0 SATURATION\n"
1412 L"<< 201 MIXER OK\n"
1413 L"<< 0.5", L"to retrieve the current saturation");
1416 std::wstring mixer_saturation_command(command_context& ctx)
1418 return single_double_animatable_mixer_command(
1420 [](const frame_transform& t) { return t.image_transform.saturation; },
1421 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1424 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1426 sink.short_description(L"Change the contrast of a layer.");
1427 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1428 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1429 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1430 sink.para()->text(L"Examples:");
1431 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1433 L">> MIXER 1-0 CONTRAST\n"
1434 L"<< 201 MIXER OK\n"
1435 L"<< 0.5", L"to retrieve the current contrast");
1438 std::wstring mixer_contrast_command(command_context& ctx)
1440 return single_double_animatable_mixer_command(
1442 [](const frame_transform& t) { return t.image_transform.contrast; },
1443 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1446 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1448 sink.short_description(L"Adjust the video levels of a layer.");
1449 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);
1451 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1453 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1454 ->item(L"gamma", L"Adjusts the gamma of the image.")
1455 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1456 sink.para()->text(L"Examples:");
1457 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");
1458 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");
1459 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1461 L">> MIXER 1-10 LEVELS\n"
1462 L"<< 201 MIXER OK\n"
1463 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1466 std::wstring mixer_levels_command(command_context& ctx)
1468 if (ctx.parameters.empty())
1470 auto levels = get_current_transform(ctx).image_transform.levels;
1471 return L"201 MIXER OK\r\n"
1472 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1473 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1474 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1475 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1476 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1479 transforms_applier transforms(ctx);
1481 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1482 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1483 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1484 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1485 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1486 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1487 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1489 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1491 transform.image_transform.levels = value;
1493 }, duration, tween));
1496 return L"202 MIXER OK\r\n";
1499 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1501 sink.short_description(L"Change the fill position and scale of a layer.");
1502 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1504 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1505 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1506 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1507 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1509 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1510 ->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.");
1511 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1513 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1514 ->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, ")
1515 ->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");
1517 ->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.")
1518 ->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.")
1519 ->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.")
1520 ->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.");
1521 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1522 sink.para()->text(L"Examples:");
1523 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1525 L">> MIXER 1-0 FILL\n"
1526 L"<< 201 MIXER OK\n"
1527 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1530 std::wstring mixer_fill_command(command_context& ctx)
1532 if (ctx.parameters.empty())
1534 auto transform = get_current_transform(ctx).image_transform;
1535 auto translation = transform.fill_translation;
1536 auto scale = transform.fill_scale;
1537 return L"201 MIXER OK\r\n"
1538 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1539 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1540 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1541 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1544 transforms_applier transforms(ctx);
1545 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1546 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1547 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1548 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1549 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1550 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1552 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1554 transform.image_transform.fill_translation[0] = x;
1555 transform.image_transform.fill_translation[1] = y;
1556 transform.image_transform.fill_scale[0] = x_s;
1557 transform.image_transform.fill_scale[1] = y_s;
1559 }, duration, tween));
1562 return L"202 MIXER OK\r\n";
1565 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1567 sink.short_description(L"Change the clipping viewport of a layer.");
1568 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1570 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1571 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1572 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1574 ->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.")
1575 ->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.")
1576 ->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.")
1577 ->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.");
1578 sink.para()->text(L"Examples:");
1579 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1581 L">> MIXER 1-0 CLIP\n"
1582 L"<< 201 MIXER OK\n"
1583 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1586 std::wstring mixer_clip_command(command_context& ctx)
1588 if (ctx.parameters.empty())
1590 auto transform = get_current_transform(ctx).image_transform;
1591 auto translation = transform.clip_translation;
1592 auto scale = transform.clip_scale;
1594 return L"201 MIXER OK\r\n"
1595 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1596 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1597 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1598 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1601 transforms_applier transforms(ctx);
1602 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1603 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1604 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1605 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1606 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1607 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1609 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1611 transform.image_transform.clip_translation[0] = x;
1612 transform.image_transform.clip_translation[1] = y;
1613 transform.image_transform.clip_scale[0] = x_s;
1614 transform.image_transform.clip_scale[1] = y_s;
1616 }, duration, tween));
1619 return L"202 MIXER OK\r\n";
1622 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1624 sink.short_description(L"Change the anchor point of a layer.");
1625 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1626 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1628 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1629 ->text(L" will be done from.");
1631 ->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.")
1632 ->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.");
1633 sink.para()->text(L"Examples:");
1634 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1636 L">> MIXER 1-10 ANCHOR\n"
1637 L"<< 201 MIXER OK\n"
1638 L"<< 0.5 0.6", L"gets the anchor point");
1641 std::wstring mixer_anchor_command(command_context& ctx)
1643 if (ctx.parameters.empty())
1645 auto transform = get_current_transform(ctx).image_transform;
1646 auto anchor = transform.anchor;
1647 return L"201 MIXER OK\r\n"
1648 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1649 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1652 transforms_applier transforms(ctx);
1653 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1654 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1655 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1656 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1658 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1660 transform.image_transform.anchor[0] = x;
1661 transform.image_transform.anchor[1] = y;
1663 }, duration, tween));
1666 return L"202 MIXER OK\r\n";
1669 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1671 sink.short_description(L"Crop a layer.");
1672 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);
1674 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1675 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1676 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1678 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1679 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1680 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1681 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1682 sink.para()->text(L"Examples:");
1683 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");
1685 L">> MIXER 1-0 CROP\n"
1686 L"<< 201 MIXER OK\n"
1687 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1690 std::wstring mixer_crop_command(command_context& ctx)
1692 if (ctx.parameters.empty())
1694 auto crop = get_current_transform(ctx).image_transform.crop;
1695 return L"201 MIXER OK\r\n"
1696 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1697 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1698 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1699 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1702 transforms_applier transforms(ctx);
1703 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1704 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1705 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1706 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1707 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1708 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1710 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1712 transform.image_transform.crop.ul[0] = ul_x;
1713 transform.image_transform.crop.ul[1] = ul_y;
1714 transform.image_transform.crop.lr[0] = lr_x;
1715 transform.image_transform.crop.lr[1] = lr_y;
1717 }, duration, tween));
1720 return L"202 MIXER OK\r\n";
1723 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1725 sink.short_description(L"Rotate a layer.");
1726 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1728 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1729 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1730 sink.para()->text(L"Examples:");
1731 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1733 L">> MIXER 1-0 ROTATION\n"
1734 L"<< 201 MIXER OK\n"
1735 L"<< 45", L"to retrieve the current angle");
1738 std::wstring mixer_rotation_command(command_context& ctx)
1740 static const double PI = 3.141592653589793;
1742 return single_double_animatable_mixer_command(
1744 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1745 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1748 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1750 sink.short_description(L"Adjust the perspective transform of a layer.");
1751 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);
1753 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1755 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1756 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1757 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1758 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1759 sink.para()->text(L"Examples:");
1760 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1762 L">> MIXER 1-10 PERSPECTIVE\n"
1763 L"<< 201 MIXER OK\n"
1764 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1767 std::wstring mixer_perspective_command(command_context& ctx)
1769 if (ctx.parameters.empty())
1771 auto perspective = get_current_transform(ctx).image_transform.perspective;
1774 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1775 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1776 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1777 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1778 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1779 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1780 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1781 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1784 transforms_applier transforms(ctx);
1785 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1786 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1787 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1788 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1789 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1790 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1791 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1792 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1793 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1794 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1796 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1798 transform.image_transform.perspective.ul[0] = ul_x;
1799 transform.image_transform.perspective.ul[1] = ul_y;
1800 transform.image_transform.perspective.ur[0] = ur_x;
1801 transform.image_transform.perspective.ur[1] = ur_y;
1802 transform.image_transform.perspective.lr[0] = lr_x;
1803 transform.image_transform.perspective.lr[1] = lr_y;
1804 transform.image_transform.perspective.ll[0] = ll_x;
1805 transform.image_transform.perspective.ll[1] = ll_y;
1807 }, duration, tween));
1810 return L"202 MIXER OK\r\n";
1813 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1815 sink.short_description(L"Enable or disable mipmapping for a layer.");
1816 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1818 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1819 ->text(L"If no argument is given the current state is returned.");
1820 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1821 sink.para()->text(L"Examples:");
1822 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1824 L">> MIXER 1-10 MIPMAP\n"
1825 L"<< 201 MIXER OK\n"
1826 L"<< 1", L"for getting the current state");
1829 std::wstring mixer_mipmap_command(command_context& ctx)
1831 if (ctx.parameters.empty())
1832 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1834 transforms_applier transforms(ctx);
1835 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1836 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1838 transform.image_transform.use_mipmap = value;
1843 return L"202 MIXER OK\r\n";
1846 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1848 sink.short_description(L"Change the volume of a layer.");
1849 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1850 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1851 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1852 sink.para()->text(L"Examples:");
1853 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1854 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1856 L">> MIXER 1-0 VOLUME\n"
1857 L"<< 201 MIXER OK\n"
1858 L"<< 0.8", L"to retrieve the current volume");
1861 std::wstring mixer_volume_command(command_context& ctx)
1863 return single_double_animatable_mixer_command(
1865 [](const frame_transform& t) { return t.audio_transform.volume; },
1866 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1869 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1871 sink.short_description(L"Change the volume of an entire channel.");
1872 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1873 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1874 sink.para()->text(L"Examples:");
1875 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1876 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1877 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1880 std::wstring mixer_mastervolume_command(command_context& ctx)
1882 if (ctx.parameters.empty())
1884 auto volume = ctx.channel.channel->mixer().get_master_volume();
1885 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1888 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1889 ctx.channel.channel->mixer().set_master_volume(master_volume);
1891 return L"202 MIXER OK\r\n";
1894 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1896 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1897 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1898 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1899 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1900 sink.para()->text(L"Examples:");
1901 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1902 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1904 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1905 L"<< 201 MIXER OK\n"
1909 std::wstring mixer_straight_alpha_command(command_context& ctx)
1911 if (ctx.parameters.empty())
1913 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1914 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1917 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1918 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1920 return L"202 MIXER OK\r\n";
1923 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1925 sink.short_description(L"Create a grid of video layers.");
1926 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1928 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1929 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1930 sink.para()->text(L"Examples:");
1931 sink.example(L">> MIXER 1 GRID 2");
1934 std::wstring mixer_grid_command(command_context& ctx)
1936 transforms_applier transforms(ctx);
1937 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1938 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1939 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1940 double delta = 1.0 / static_cast<double>(n);
1941 for (int x = 0; x < n; ++x)
1943 for (int y = 0; y < n; ++y)
1945 int index = x + y*n + 1;
1946 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1948 transform.image_transform.fill_translation[0] = x*delta;
1949 transform.image_transform.fill_translation[1] = y*delta;
1950 transform.image_transform.fill_scale[0] = delta;
1951 transform.image_transform.fill_scale[1] = delta;
1952 transform.image_transform.clip_translation[0] = x*delta;
1953 transform.image_transform.clip_translation[1] = y*delta;
1954 transform.image_transform.clip_scale[0] = delta;
1955 transform.image_transform.clip_scale[1] = delta;
1957 }, duration, tween));
1962 return L"202 MIXER OK\r\n";
1965 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1967 sink.short_description(L"Commit all deferred mixer transforms.");
1968 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1969 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1970 sink.para()->text(L"Examples:");
1972 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1973 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1974 L">> MIXER 1 COMMIT");
1977 std::wstring mixer_commit_command(command_context& ctx)
1979 transforms_applier transforms(ctx);
1980 transforms.commit_deferred();
1982 return L"202 MIXER OK\r\n";
1985 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1987 sink.short_description(L"Clear all transformations on a channel or layer.");
1988 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1989 sink.para()->text(L"Clears all transformations on a channel or layer.");
1990 sink.para()->text(L"Examples:");
1991 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1992 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1995 std::wstring mixer_clear_command(command_context& ctx)
1997 int layer = ctx.layer_id;
2000 ctx.channel.channel->stage().clear_transforms();
2002 ctx.channel.channel->stage().clear_transforms(layer);
2004 return L"202 MIXER OK\r\n";
2007 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2009 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2010 sink.syntax(L"CHANNEL_GRID");
2011 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2013 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2014 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2017 std::wstring channel_grid_command(command_context& ctx)
2020 auto self = ctx.channels.back();
2022 core::diagnostics::scoped_call_context save;
2023 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2025 std::vector<std::wstring> params;
2026 params.push_back(L"SCREEN");
2027 params.push_back(L"0");
2028 params.push_back(L"NAME");
2029 params.push_back(L"Channel Grid Window");
2030 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
2032 self.channel->output().add(screen);
2034 for (auto& channel : ctx.channels)
2036 if (channel.channel != self.channel)
2038 core::diagnostics::call_context::for_thread().layer = index;
2039 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2040 self.channel->stage().load(index, producer, false);
2041 self.channel->stage().play(index);
2046 auto num_channels = ctx.channels.size() - 1;
2047 int square_side_length = std::ceil(std::sqrt(num_channels));
2049 ctx.channel_index = self.channel->index();
2051 ctx.parameters.clear();
2052 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2053 mixer_grid_command(ctx);
2055 return L"202 CHANNEL_GRID OK\r\n";
2058 // Thumbnail Commands
2060 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2062 sink.short_description(L"List all thumbnails.");
2063 sink.syntax(L"THUMBNAIL LIST");
2064 sink.para()->text(L"Lists all thumbnails.");
2065 sink.para()->text(L"Examples:");
2067 L">> THUMBNAIL LIST\n"
2068 L"<< 200 THUMBNAIL LIST OK\n"
2069 L"<< \"AMB\" 20130301T124409 1149\n"
2070 L"<< \"foo/bar\" 20130523T234001 244");
2073 std::wstring thumbnail_list_command(command_context& ctx)
2075 std::wstringstream replyString;
2076 replyString << L"200 THUMBNAIL LIST OK\r\n";
2078 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2080 if (boost::filesystem::is_regular_file(itr->path()))
2082 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2085 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
2087 auto str = relativePath.replace_extension(L"").generic_wstring();
2088 if (str[0] == '\\' || str[0] == '/')
2089 str = std::wstring(str.begin() + 1, str.end());
2091 auto mtime = boost::filesystem::last_write_time(itr->path());
2092 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2093 auto file_size = boost::filesystem::file_size(itr->path());
2095 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2099 replyString << L"\r\n";
2101 return boost::to_upper_copy(replyString.str());
2104 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2106 sink.short_description(L"Retrieve a thumbnail.");
2107 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2108 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2109 sink.para()->text(L"Examples:");
2111 L">> THUMBNAIL RETRIEVE foo/bar\n"
2112 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2113 L"<< ...base64 data...");
2116 std::wstring thumbnail_retrieve_command(command_context& ctx)
2118 std::wstring filename = env::thumbnails_folder();
2119 filename.append(ctx.parameters.at(0));
2120 filename.append(L".png");
2122 std::wstring file_contents;
2124 auto found_file = find_case_insensitive(filename);
2127 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2129 if (file_contents.empty())
2130 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2132 std::wstringstream reply;
2134 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2135 reply << file_contents;
2140 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2142 sink.short_description(L"Regenerate a thumbnail.");
2143 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2144 sink.para()->text(L"Regenerates a thumbnail.");
2147 std::wstring thumbnail_generate_command(command_context& ctx)
2151 ctx.thumb_gen->generate(ctx.parameters.at(0));
2152 return L"202 THUMBNAIL GENERATE OK\r\n";
2155 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2158 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2160 sink.short_description(L"Regenerate all thumbnails.");
2161 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2162 sink.para()->text(L"Regenerates all thumbnails.");
2165 std::wstring thumbnail_generateall_command(command_context& ctx)
2169 ctx.thumb_gen->generate_all();
2170 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2173 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2178 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2180 sink.short_description(L"Get information about a media file.");
2181 sink.syntax(L"CINF [filename:string]");
2182 sink.para()->text(L"Returns information about a media file.");
2185 std::wstring cinf_command(command_context& ctx)
2188 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2190 auto path = itr->path();
2191 auto file = path.replace_extension(L"").filename().wstring();
2192 if (boost::iequals(file, ctx.parameters.at(0)))
2193 info += MediaInfo(itr->path(), ctx.media_info_repo);
2197 CASPAR_THROW_EXCEPTION(file_not_found());
2199 std::wstringstream replyString;
2200 replyString << L"200 CINF OK\r\n";
2201 replyString << info << "\r\n";
2203 return replyString.str();
2206 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2208 sink.short_description(L"List all media files.");
2209 sink.syntax(L"CLS");
2211 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2212 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2215 std::wstring cls_command(command_context& ctx)
2217 std::wstringstream replyString;
2218 replyString << L"200 CLS OK\r\n";
2219 replyString << ListMedia(ctx.media_info_repo);
2220 replyString << L"\r\n";
2221 return boost::to_upper_copy(replyString.str());
2224 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2226 sink.short_description(L"List all templates.");
2227 sink.syntax(L"TLS");
2229 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2230 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2233 std::wstring tls_command(command_context& ctx)
2235 std::wstringstream replyString;
2236 replyString << L"200 TLS OK\r\n";
2238 replyString << ListTemplates(ctx.cg_registry);
2239 replyString << L"\r\n";
2241 return replyString.str();
2244 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2246 sink.short_description(L"Get version information.");
2247 sink.syntax(L"VERSION {[component:string]}");
2248 sink.para()->text(L"Returns the version of specified component.");
2249 sink.para()->text(L"Examples:");
2252 L"<< 201 VERSION OK\n"
2253 L"<< 2.1.0.f207a33 STABLE");
2255 L">> VERSION SERVER\n"
2256 L"<< 201 VERSION OK\n"
2257 L"<< 2.1.0.f207a33 STABLE");
2259 L">> VERSION FLASH\n"
2260 L"<< 201 VERSION OK\n"
2264 std::wstring version_command(command_context& ctx)
2266 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2268 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2270 return L"201 VERSION OK\r\n" + version + L"\r\n";
2273 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2276 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2278 sink.short_description(L"Get a list of the available channels.");
2279 sink.syntax(L"INFO");
2280 sink.para()->text(L"Retrieves a list of the available channels.");
2284 L"<< 1 720p5000 PLAYING\n"
2285 L"<< 2 PAL PLAYING");
2288 std::wstring info_command(command_context& ctx)
2290 std::wstringstream replyString;
2291 // This is needed for backwards compatibility with old clients
2292 replyString << L"200 INFO OK\r\n";
2293 for (size_t n = 0; n < ctx.channels.size(); ++n)
2294 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2295 replyString << L"\r\n";
2296 return replyString.str();
2299 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2301 std::wstringstream replyString;
2303 if (command.empty())
2304 replyString << L"201 INFO OK\r\n";
2306 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2308 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2309 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2310 replyString << L"\r\n";
2311 return replyString.str();
2314 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2316 sink.short_description(L"Get information about a channel or a layer.");
2317 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2318 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2319 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2322 std::wstring info_channel_command(command_context& ctx)
2324 boost::property_tree::wptree info;
2325 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2327 if (layer == std::numeric_limits<int>::min())
2329 info.add_child(L"channel", ctx.channel.channel->info())
2330 .add(L"index", ctx.channel_index);
2334 if (ctx.parameters.size() >= 1)
2336 if (boost::iequals(ctx.parameters.at(0), L"B"))
2337 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2339 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2343 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2347 return create_info_xml_reply(info);
2350 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2352 sink.short_description(L"Get information about a template.");
2353 sink.syntax(L"INFO TEMPLATE [template:string]");
2354 sink.para()->text(L"Gets information about the specified template.");
2357 std::wstring info_template_command(command_context& ctx)
2359 auto filename = ctx.parameters.at(0);
2361 std::wstringstream str;
2362 str << u16(ctx.cg_registry->read_meta_info(filename));
2363 boost::property_tree::wptree info;
2364 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2366 return create_info_xml_reply(info, L"TEMPLATE");
2369 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2371 sink.short_description(L"Get the contents of the configuration used.");
2372 sink.syntax(L"INFO CONFIG");
2373 sink.para()->text(L"Gets the contents of the configuration used.");
2376 std::wstring info_config_command(command_context& ctx)
2378 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2381 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2383 sink.short_description(L"Get information about the paths used.");
2384 sink.syntax(L"INFO PATHS");
2385 sink.para()->text(L"Gets information about the paths used.");
2388 std::wstring info_paths_command(command_context& ctx)
2390 boost::property_tree::wptree info;
2391 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2392 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2394 return create_info_xml_reply(info, L"PATHS");
2397 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2399 sink.short_description(L"Get system information.");
2400 sink.syntax(L"INFO SYSTEM");
2401 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2404 std::wstring info_system_command(command_context& ctx)
2406 boost::property_tree::wptree info;
2408 info.add(L"system.name", caspar::system_product_name());
2409 info.add(L"system.os.description", caspar::os_description());
2410 info.add(L"system.cpu", caspar::cpu_info());
2412 ctx.system_info_repo->fill_information(info);
2414 return create_info_xml_reply(info, L"SYSTEM");
2417 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2419 sink.short_description(L"Get detailed information about all channels.");
2420 sink.syntax(L"INFO SERVER");
2421 sink.para()->text(L"Gets detailed information about all channels.");
2424 std::wstring info_server_command(command_context& ctx)
2426 boost::property_tree::wptree info;
2429 for (auto& channel : ctx.channels)
2430 info.add_child(L"channels.channel", channel.channel->info())
2431 .add(L"index", ++index);
2433 return create_info_xml_reply(info, L"SERVER");
2436 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2438 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2439 sink.syntax(L"INFO QUEUES");
2440 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2443 std::wstring info_queues_command(command_context& ctx)
2445 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2448 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2450 sink.short_description(L"Lists all known threads in the server.");
2451 sink.syntax(L"INFO THREADS");
2452 sink.para()->text(L"Lists all known threads in the server.");
2455 std::wstring info_threads_command(command_context& ctx)
2457 std::wstringstream replyString;
2458 replyString << L"200 INFO THREADS OK\r\n";
2460 for (auto& thread : get_thread_infos())
2462 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2465 replyString << L"\r\n";
2466 return replyString.str();
2469 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2471 sink.short_description(L"Get the current delay on a channel or a layer.");
2472 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2473 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2476 std::wstring info_delay_command(command_context& ctx)
2478 boost::property_tree::wptree info;
2479 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2481 if (layer == std::numeric_limits<int>::min())
2482 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2484 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2485 .add(L"index", layer);
2487 return create_info_xml_reply(info, L"DELAY");
2490 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2492 sink.short_description(L"Open the diagnostics window.");
2493 sink.syntax(L"DIAG");
2494 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2497 std::wstring diag_command(command_context& ctx)
2499 core::diagnostics::osd::show_graphs(true);
2501 return L"202 DIAG OK\r\n";
2504 static const int WIDTH = 80;
2506 struct max_width_sink : public core::help_sink
2508 std::size_t max_width = 0;
2510 void begin_item(const std::wstring& name) override
2512 max_width = std::max(name.length(), max_width);
2516 struct short_description_sink : public core::help_sink
2519 std::wstringstream& out;
2521 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2523 void begin_item(const std::wstring& name) override
2525 out << std::left << std::setw(width + 1) << name;
2528 void short_description(const std::wstring& short_description) override
2530 out << short_description << L"\r\n";
2534 struct simple_paragraph_builder : core::paragraph_builder
2536 std::wostringstream out;
2537 std::wstringstream& commit_to;
2539 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2540 ~simple_paragraph_builder()
2542 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2544 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2546 out << std::move(text);
2547 return shared_from_this();
2549 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2550 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2551 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2554 struct simple_definition_list_builder : core::definition_list_builder
2556 std::wstringstream& out;
2558 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2559 ~simple_definition_list_builder()
2564 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2566 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2567 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2568 return shared_from_this();
2572 struct long_description_sink : public core::help_sink
2574 std::wstringstream& out;
2576 long_description_sink(std::wstringstream& out) : out(out) { }
2578 void syntax(const std::wstring& syntax) override
2580 out << L"Syntax:\n";
2581 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2584 spl::shared_ptr<core::paragraph_builder> para() override
2586 return spl::make_shared<simple_paragraph_builder>(out);
2589 spl::shared_ptr<core::definition_list_builder> definitions() override
2591 return spl::make_shared<simple_definition_list_builder>(out);
2594 void example(const std::wstring& code, const std::wstring& caption) override
2596 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2598 if (!caption.empty())
2599 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2604 void begin_item(const std::wstring& name) override
2606 out << name << L"\n\n";
2610 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2612 std::wstringstream result;
2613 result << L"200 " << help_command << L" OK\r\n";
2614 max_width_sink width;
2615 ctx.help_repo->help(tags, width);
2616 short_description_sink sink(width.max_width, result);
2617 sink.width = width.max_width;
2618 ctx.help_repo->help(tags, sink);
2620 return result.str();
2623 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2625 std::wstringstream result;
2626 result << L"201 " << help_command << L" OK\r\n";
2627 auto joined = boost::join(ctx.parameters, L" ");
2628 long_description_sink sink(result);
2629 ctx.help_repo->help(tags, joined, sink);
2631 return result.str();
2634 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2636 sink.short_description(L"Show online help for AMCP commands.");
2637 sink.syntax(LR"(HELP {[command:string]})");
2638 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2639 sink.example(L">> HELP", L"Shows a list of commands.");
2640 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2643 std::wstring help_command(command_context& ctx)
2645 if (ctx.parameters.size() == 0)
2646 return create_help_list(L"HELP", ctx, { L"AMCP" });
2648 return create_help_details(L"HELP", ctx, { L"AMCP" });
2651 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2653 sink.short_description(L"Show online help for producers.");
2654 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2655 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2656 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2657 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2660 std::wstring help_producer_command(command_context& ctx)
2662 if (ctx.parameters.size() == 0)
2663 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2665 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2668 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2670 sink.short_description(L"Show online help for consumers.");
2671 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2672 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2673 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2674 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2677 std::wstring help_consumer_command(command_context& ctx)
2679 if (ctx.parameters.size() == 0)
2680 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2682 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2685 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2687 sink.short_description(L"Disconnect the session.");
2688 sink.syntax(L"BYE");
2690 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2691 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2694 std::wstring bye_command(command_context& ctx)
2696 ctx.client->disconnect();
2700 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2702 sink.short_description(L"Shutdown the server.");
2703 sink.syntax(L"KILL");
2704 sink.para()->text(L"Shuts the server down.");
2707 std::wstring kill_command(command_context& ctx)
2709 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2710 return L"202 KILL OK\r\n";
2713 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2715 sink.short_description(L"Shutdown the server with restart exit code.");
2716 sink.syntax(L"RESTART");
2718 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2719 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2722 std::wstring restart_command(command_context& ctx)
2724 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2725 return L"202 RESTART OK\r\n";
2728 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2730 sink.short_description(L"Lock or unlock access to a channel.");
2731 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2732 sink.para()->text(L"Allows for exclusive access to a channel.");
2733 sink.para()->text(L"Examples:");
2734 sink.example(L"LOCK 1 ACQUIRE secret");
2735 sink.example(L"LOCK 1 RELEASE");
2736 sink.example(L"LOCK 1 CLEAR");
2739 std::wstring lock_command(command_context& ctx)
2741 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2742 auto lock = ctx.channels.at(channel_index).lock;
2743 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2745 if (command == L"ACQUIRE")
2747 std::wstring lock_phrase = ctx.parameters.at(2);
2749 //TODO: read options
2751 //just lock one channel
2752 if (!lock->try_lock(lock_phrase, ctx.client))
2753 return L"503 LOCK ACQUIRE FAILED\r\n";
2755 return L"202 LOCK ACQUIRE OK\r\n";
2757 else if (command == L"RELEASE")
2759 lock->release_lock(ctx.client);
2760 return L"202 LOCK RELEASE OK\r\n";
2762 else if (command == L"CLEAR")
2764 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2765 std::wstring client_override_phrase;
2767 if (!override_phrase.empty())
2768 client_override_phrase = ctx.parameters.at(2);
2770 //just clear one channel
2771 if (client_override_phrase != override_phrase)
2772 return L"503 LOCK CLEAR FAILED\r\n";
2774 lock->clear_locks();
2776 return L"202 LOCK CLEAR OK\r\n";
2779 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2782 void register_commands(amcp_command_repository& repo)
2784 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2785 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2786 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2787 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2788 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2789 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2790 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2791 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2792 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2793 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2794 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2795 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2796 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2797 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2798 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2800 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2801 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2802 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2803 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2805 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2806 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2807 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2808 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2809 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2810 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2811 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2812 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2813 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2815 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2816 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2817 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2818 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2819 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2820 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2821 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2822 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2823 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2824 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2825 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2826 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2827 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2828 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2829 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2830 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2831 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2832 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2833 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2834 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2835 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2836 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2838 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2839 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2840 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2841 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2843 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2844 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2845 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2846 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2847 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2848 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2849 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2850 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2851 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2852 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2853 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2854 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2855 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2856 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2857 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2858 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2859 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2860 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2861 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2862 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2863 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2867 }} //namespace caspar