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 auto template_type = cg_registry->get_cg_producer_name(str);
271 replyString << L"\"" << str
272 << L"\" " << sizeWStr
273 << L" " << writeTimeWStr
274 << L" " << template_type
278 return replyString.str();
281 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
283 return core::frame_producer_dependencies(
284 channel->frame_factory(),
285 cpplinq::from(ctx.channels)
286 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
288 channel->video_format_desc(),
289 ctx.producer_registry);
294 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
296 sink.short_description(L"Load a media file or resource in the background.");
297 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]})");
299 ->text(L"Loads a producer in the background and prepares it for playout. ")
300 ->text(L"If no layer is specified the default layer index will be used.");
302 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
303 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
305 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
306 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
308 ->code(L"loop")->text(L" will cause the clip to loop.");
310 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
312 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
314 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
315 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
316 sink.para()->text(L"Examples:");
317 sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
318 sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
319 sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
320 sink.example(L">> LOADBG 1-0 MY_FILE");
322 L">> PLAY 1-1 MY_FILE\n"
323 L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
324 L"To automatically fade out a layer after a video file has been played to the end");
326 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
328 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
329 ->code(L"filter")->text(L" command.");
332 std::wstring loadbg_command(command_context& ctx)
334 transition_info transitionInfo;
338 std::wstring message;
339 for (size_t n = 0; n < ctx.parameters.size(); ++n)
340 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
342 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)?.*)");
344 if (boost::regex_match(message, what, expr))
346 auto transition = what["TRANSITION"].str();
347 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
348 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
349 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
350 transitionInfo.tweener = tween;
352 if (transition == L"CUT")
353 transitionInfo.type = transition_type::cut;
354 else if (transition == L"MIX")
355 transitionInfo.type = transition_type::mix;
356 else if (transition == L"PUSH")
357 transitionInfo.type = transition_type::push;
358 else if (transition == L"SLIDE")
359 transitionInfo.type = transition_type::slide;
360 else if (transition == L"WIPE")
361 transitionInfo.type = transition_type::wipe;
363 if (direction == L"FROMLEFT")
364 transitionInfo.direction = transition_direction::from_left;
365 else if (direction == L"FROMRIGHT")
366 transitionInfo.direction = transition_direction::from_right;
367 else if (direction == L"LEFT")
368 transitionInfo.direction = transition_direction::from_right;
369 else if (direction == L"RIGHT")
370 transitionInfo.direction = transition_direction::from_left;
373 //Perform loading of the clip
374 core::diagnostics::scoped_call_context save;
375 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
376 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
378 auto channel = ctx.channel.channel;
379 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
381 if (pFP == frame_producer::empty())
382 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
384 bool auto_play = contains_param(L"AUTO", ctx.parameters);
386 auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
388 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
390 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
392 return L"202 LOADBG OK\r\n";
395 void load_describer(core::help_sink& sink, const core::help_repository& repo)
397 sink.short_description(L"Load a media file or resource to the foreground.");
398 sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
400 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
401 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
402 sink.para()->text(L"Examples:");
403 sink.example(L">> LOAD 1 MY_FILE");
404 sink.example(L">> LOAD 1-1 MY_FILE");
405 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
408 std::wstring load_command(command_context& ctx)
410 core::diagnostics::scoped_call_context save;
411 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
412 core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
413 auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
414 ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
416 return L"202 LOAD OK\r\n";
419 void play_describer(core::help_sink& sink, const core::help_repository& repository)
421 sink.short_description(L"Play a media file or resource.");
422 sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
424 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
425 ->text(L") is prepared, it will be executed.");
427 ->text(L"If additional parameters (see ")->see(L"LOADBG")
428 ->text(L") are provided then the provided clip will first be loaded to the background.");
429 sink.para()->text(L"Examples:");
430 sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
431 sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
432 sink.example(L">> PLAY 1-0 MY_FILE");
433 sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
436 std::wstring play_command(command_context& ctx)
438 if (!ctx.parameters.empty())
441 ctx.channel.channel->stage().play(ctx.layer_index());
443 return L"202 PLAY OK\r\n";
446 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
448 sink.short_description(L"Pause playback of a layer.");
449 sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
451 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
452 ->text(L" command can be used to resume playback again.");
453 sink.para()->text(L"Examples:");
454 sink.example(L">> PAUSE 1");
455 sink.example(L">> PAUSE 1-1");
458 std::wstring pause_command(command_context& ctx)
460 ctx.channel.channel->stage().pause(ctx.layer_index());
461 return L"202 PAUSE OK\r\n";
464 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
466 sink.short_description(L"Resume playback of a layer.");
467 sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
469 ->text(L"Resumes playback of a foreground clip previously paused with the ")
470 ->see(L"PAUSE")->text(L" command.");
471 sink.para()->text(L"Examples:");
472 sink.example(L">> RESUME 1");
473 sink.example(L">> RESUME 1-1");
476 std::wstring resume_command(command_context& ctx)
478 ctx.channel.channel->stage().resume(ctx.layer_index());
479 return L"202 RESUME OK\r\n";
482 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
484 sink.short_description(L"Remove the foreground clip of a layer.");
485 sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
487 ->text(L"Removes the foreground clip of the specified layer.");
488 sink.para()->text(L"Examples:");
489 sink.example(L">> STOP 1");
490 sink.example(L">> STOP 1-1");
493 std::wstring stop_command(command_context& ctx)
495 ctx.channel.channel->stage().stop(ctx.layer_index());
496 return L"202 STOP OK\r\n";
499 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
501 sink.short_description(L"Remove all clips of a layer or an entire channel.");
502 sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
504 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
505 ->text(L"If no layer is specified then all layers in the specified ")
506 ->code(L"video_channel")->text(L" are cleared.");
507 sink.para()->text(L"Examples:");
508 sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
509 sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
512 std::wstring clear_command(command_context& ctx)
514 int index = ctx.layer_index(std::numeric_limits<int>::min());
515 if (index != std::numeric_limits<int>::min())
516 ctx.channel.channel->stage().clear(index);
518 ctx.channel.channel->stage().clear();
520 return L"202 CLEAR OK\r\n";
523 void call_describer(core::help_sink& sink, const core::help_repository& repo)
525 sink.short_description(L"Call a method on a producer.");
526 sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
528 ->text(L"Calls method on the specified producer with the provided ")
529 ->code(L"param")->text(L" string.");
530 sink.para()->text(L"Examples:");
531 sink.example(L">> CALL 1 LOOP");
532 sink.example(L">> CALL 1-2 SEEK 25");
535 std::wstring call_command(command_context& ctx)
537 auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters);
539 // TODO: because of std::async deferred timed waiting does not work
541 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
542 if (wait_res == std::future_status::timeout)
543 CASPAR_THROW_EXCEPTION(timed_out());*/
545 std::wstringstream replyString;
546 if (result.get().empty())
547 replyString << L"202 CALL OK\r\n";
549 replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
551 return replyString.str();
554 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
556 sink.short_description(L"Swap layers between channels.");
557 sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
559 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
560 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
561 sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
562 sink.para()->text(L"Examples:");
563 sink.example(L">> SWAP 1 2");
564 sink.example(L">> SWAP 1-1 2-3");
565 sink.example(L">> SWAP 1-1 1-2");
566 sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
569 std::wstring swap_command(command_context& ctx)
571 bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
573 if (ctx.layer_index(-1) != -1)
575 std::vector<std::string> strs;
576 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
578 auto ch1 = ctx.channel.channel;
579 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
581 int l1 = ctx.layer_index();
582 int l2 = boost::lexical_cast<int>(strs.at(1));
584 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
588 auto ch1 = ctx.channel.channel;
589 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
590 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
593 return L"202 SWAP OK\r\n";
596 void add_describer(core::help_sink& sink, const core::help_repository& repo)
598 sink.short_description(L"Add a consumer to a video channel.");
599 sink.syntax(L"ADD [video_channel:int] [consumer:string] [parameters:string]");
601 ->text(L"Adds a consumer to the specified video channel. The string ")
602 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
603 ->text(L"If a successful match is found a consumer will be created and added to the ")
604 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
605 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
606 ->see(L"the CasparCG config file")->text(L".");
607 sink.para()->text(L"Examples:");
608 sink.example(L">> ADD 1 DECKLINK 1");
609 sink.example(L">> ADD 1 BLUEFISH 2");
610 sink.example(L">> ADD 1 SCREEN");
611 sink.example(L">> ADD 1 AUDIO");
612 sink.example(L">> ADD 1 IMAGE filename");
613 sink.example(L">> ADD 1 FILE filename.mov");
614 sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
615 sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
616 sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
619 std::wstring add_command(command_context& ctx)
621 replace_placeholders(
622 L"<CLIENT_IP_ADDRESS>",
623 ctx.client->address(),
626 core::diagnostics::scoped_call_context save;
627 core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
629 auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage());
630 ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
632 return L"202 ADD OK\r\n";
635 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
637 sink.short_description(L"Remove a consumer from a video channel.");
638 sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
640 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
641 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
642 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
643 sink.para()->text(L"Examples:");
644 sink.example(L">> REMOVE 1 DECKLINK 1");
645 sink.example(L">> REMOVE 1 BLUEFISH 2");
646 sink.example(L">> REMOVE 1 SCREEN");
647 sink.example(L">> REMOVE 1 AUDIO");
648 sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
651 std::wstring remove_command(command_context& ctx)
653 auto index = ctx.layer_index(std::numeric_limits<int>::min());
655 if (index == std::numeric_limits<int>::min())
657 replace_placeholders(
658 L"<CLIENT_IP_ADDRESS>",
659 ctx.client->address(),
662 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage())->index();
665 ctx.channel.channel->output().remove(index);
667 return L"202 REMOVE OK\r\n";
670 void print_describer(core::help_sink& sink, const core::help_repository& repo)
672 sink.short_description(L"Take a snapshot of a channel.");
673 sink.syntax(L"PRINT [video_channel:int]");
675 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
676 ->code(L"media")->text(L" folder.");
677 sink.para()->text(L"Examples:");
678 sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
681 std::wstring print_command(command_context& ctx)
683 ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage()));
685 return L"202 PRINT OK\r\n";
688 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
690 sink.short_description(L"Change the log level of the server.");
691 sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
692 sink.para()->text(L"Changes the log level of the server.");
693 sink.para()->text(L"Examples:");
694 sink.example(L">> LOG LEVEL trace");
695 sink.example(L">> LOG LEVEL info");
698 std::wstring log_level_command(command_context& ctx)
700 log::set_log_level(ctx.parameters.at(0));
702 return L"202 LOG OK\r\n";
705 void set_describer(core::help_sink& sink, const core::help_repository& repo)
707 sink.short_description(L"Change the value of a channel variable.");
708 sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
709 sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
711 ->item(L"MODE", L"Changes the video format of the channel.")
712 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
713 sink.para()->text(L"Examples:");
714 sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
715 sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
718 std::wstring set_command(command_context& ctx)
720 std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
721 std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
725 auto format_desc = core::video_format_desc(value);
726 if (format_desc.format != core::video_format::invalid)
728 ctx.channel.channel->video_format_desc(format_desc);
729 return L"202 SET MODE OK\r\n";
732 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid video mode"));
734 else if (name == L"CHANNEL_LAYOUT")
736 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
740 ctx.channel.channel->audio_channel_layout(*channel_layout);
741 return L"202 SET CHANNEL_LAYOUT OK\r\n";
744 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid audio channel layout"));
747 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"Invalid channel variable"));
750 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
752 sink.short_description(L"Store a dataset.");
753 sink.syntax(L"DATA STORE [name:string] [data:string]");
754 sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
755 sink.para()->text(L"Directories will be created if they do not exist.");
756 sink.para()->text(L"Examples:");
757 sink.example(LR"(>> DATA STORE my_data "Some useful data")");
758 sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
761 std::wstring data_store_command(command_context& ctx)
763 std::wstring filename = env::data_folder();
764 filename.append(ctx.parameters[0]);
765 filename.append(L".ftd");
767 auto data_path = boost::filesystem::path(filename).parent_path().wstring();
768 auto found_data_path = find_case_insensitive(data_path);
771 data_path = *found_data_path;
773 if (!boost::filesystem::exists(data_path))
774 boost::filesystem::create_directories(data_path);
776 auto found_filename = find_case_insensitive(filename);
779 filename = *found_filename; // Overwrite case insensitive.
781 boost::filesystem::wofstream datafile(filename);
783 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
785 datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
786 datafile << ctx.parameters[1] << std::flush;
789 return L"202 DATA STORE OK\r\n";
792 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
794 sink.short_description(L"Retrieve a dataset.");
795 sink.syntax(L"DATA RETRIEVE [name:string] [data:string]");
796 sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
797 sink.para()->text(L"Examples:");
798 sink.example(L">> DATA RETRIEVE my_data");
799 sink.example(L">> DATA RETRIEVE Folder1/my_data");
802 std::wstring data_retrieve_command(command_context& ctx)
804 std::wstring filename = env::data_folder();
805 filename.append(ctx.parameters[0]);
806 filename.append(L".ftd");
808 std::wstring file_contents;
810 auto found_file = find_case_insensitive(filename);
813 file_contents = read_file(boost::filesystem::path(*found_file));
815 if (file_contents.empty())
816 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
818 std::wstringstream reply;
819 reply << L"201 DATA RETRIEVE OK\r\n";
821 std::wstringstream file_contents_stream(file_contents);
824 bool firstLine = true;
825 while (std::getline(file_contents_stream, line))
839 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
841 sink.short_description(L"List stored datasets.");
842 sink.syntax(L"DATA LIST");
843 sink.para()->text(L"Returns a list of all stored datasets.");
846 std::wstring data_list_command(command_context& ctx)
848 std::wstringstream replyString;
849 replyString << L"200 DATA LIST OK\r\n";
851 for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
853 if (boost::filesystem::is_regular_file(itr->path()))
855 if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
858 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size() - 1, itr->path().wstring().size()));
860 auto str = relativePath.replace_extension(L"").generic_wstring();
861 if (str[0] == L'\\' || str[0] == L'/')
862 str = std::wstring(str.begin() + 1, str.end());
864 replyString << str << L"\r\n";
868 replyString << L"\r\n";
870 return boost::to_upper_copy(replyString.str());
873 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
875 sink.short_description(L"Remove a stored dataset.");
876 sink.syntax(L"DATA REMOVE [name:string]");
877 sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
878 sink.para()->text(L"Examples:");
879 sink.example(L">> DATA REMOVE my_data");
880 sink.example(L">> DATA REMOVE Folder1/my_data");
883 std::wstring data_remove_command(command_context& ctx)
885 std::wstring filename = env::data_folder();
886 filename.append(ctx.parameters[0]);
887 filename.append(L".ftd");
889 if (!boost::filesystem::exists(filename))
890 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
892 if (!boost::filesystem::remove(filename))
893 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
895 return L"201 DATA REMOVE OK\r\n";
898 // Template Graphics Commands
900 int get_and_validate_layer(const std::wstring& layerstring) {
901 int length = layerstring.length();
902 for (int i = 0; i < length; ++i) {
903 if (!std::isdigit(layerstring[i])) {
904 CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(layerstring + L" is not a layer"));
908 return boost::lexical_cast<int>(layerstring);
911 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
913 sink.short_description(L"Prepare a template for displaying.");
914 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
916 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
917 ->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.");
918 sink.para()->text(L"Examples:");
919 sink.example(L"CG 1 ADD 10 svtnews/info 1");
922 std::wstring cg_add_command(command_context& ctx)
924 //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
926 int layer = get_and_validate_layer(ctx.parameters.at(0));
927 std::wstring label; //_parameters[2]
928 bool bDoStart = false; //_parameters[2] alt. _parameters[3]
929 unsigned int dataIndex = 3;
931 if (ctx.parameters.at(2).length() > 1)
933 label = ctx.parameters.at(2);
936 if (ctx.parameters.at(3).length() > 0) //read play-on-load-flag
937 bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
940 { //read play-on-load-flag
941 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
944 const wchar_t* pDataString = 0;
945 std::wstring dataFromFile;
946 if (ctx.parameters.size() > dataIndex)
948 const std::wstring& dataString = ctx.parameters.at(dataIndex);
950 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
951 pDataString = dataString.c_str();
954 //The data is not an XML-string, it must be a filename
955 std::wstring filename = env::data_folder();
956 filename.append(dataString);
957 filename.append(L".ftd");
959 auto found_file = find_case_insensitive(filename);
963 dataFromFile = read_file(boost::filesystem::path(*found_file));
964 pDataString = dataFromFile.c_str();
969 auto filename = ctx.parameters.at(1);
970 auto proxy = ctx.cg_registry->get_or_create_proxy(
971 spl::make_shared_ptr(ctx.channel.channel),
972 get_producer_dependencies(ctx.channel.channel, ctx),
973 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
976 if (proxy == core::cg_proxy::empty())
977 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
979 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
981 return L"202 CG OK\r\n";
984 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
986 sink.short_description(L"Play and display a template.");
987 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
988 sink.para()->text(L"Plays and displays the template in the specified layer.");
989 sink.para()->text(L"Examples:");
990 sink.example(L"CG 1 PLAY 0");
993 std::wstring cg_play_command(command_context& ctx)
995 int layer = get_and_validate_layer(ctx.parameters.at(0));
996 ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
998 return L"202 CG OK\r\n";
1001 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1003 auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1005 if (proxy == cg_proxy::empty())
1006 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No CG proxy running on layer"));
1011 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1013 sink.short_description(L"Stop and remove a template.");
1014 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1016 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1017 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1018 sink.para()->text(L"Examples:");
1019 sink.example(L"CG 1 STOP 0");
1022 std::wstring cg_stop_command(command_context& ctx)
1024 int layer = get_and_validate_layer(ctx.parameters.at(0));
1025 get_expected_cg_proxy(ctx)->stop(layer, 0);
1027 return L"202 CG OK\r\n";
1030 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1032 sink.short_description(LR"(Trigger a "continue" in a template.)");
1033 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1035 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1036 ->text(L"This is used to control animations that has multiple discreet steps.");
1037 sink.para()->text(L"Examples:");
1038 sink.example(L"CG 1 NEXT 0");
1041 std::wstring cg_next_command(command_context& ctx)
1043 int layer = get_and_validate_layer(ctx.parameters.at(0));
1044 get_expected_cg_proxy(ctx)->next(layer);
1046 return L"202 CG OK\r\n";
1049 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1051 sink.short_description(L"Remove a template.");
1052 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1053 sink.para()->text(L"Removes the template from the specified layer.");
1054 sink.para()->text(L"Examples:");
1055 sink.example(L"CG 1 REMOVE 0");
1058 std::wstring cg_remove_command(command_context& ctx)
1060 int layer = get_and_validate_layer(ctx.parameters.at(0));
1061 get_expected_cg_proxy(ctx)->remove(layer);
1063 return L"202 CG OK\r\n";
1066 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1068 sink.short_description(L"Remove all templates on a video layer.");
1069 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1070 sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1071 sink.para()->text(L"Examples:");
1072 sink.example(L"CG 1 CLEAR");
1075 std::wstring cg_clear_command(command_context& ctx)
1077 ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1079 return L"202 CG OK\r\n";
1082 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1084 sink.short_description(L"Update a template with new data.");
1085 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1086 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.");
1089 std::wstring cg_update_command(command_context& ctx)
1091 int layer = get_and_validate_layer(ctx.parameters.at(0));
1093 std::wstring dataString = ctx.parameters.at(1);
1094 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1096 //The data is not XML or Json, it must be a filename
1097 std::wstring filename = env::data_folder();
1098 filename.append(dataString);
1099 filename.append(L".ftd");
1101 dataString = read_file(boost::filesystem::path(filename));
1104 get_expected_cg_proxy(ctx)->update(layer, dataString);
1106 return L"202 CG OK\r\n";
1109 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1111 sink.short_description(L"Invoke a method/label on a template.");
1112 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1113 sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1114 sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1117 std::wstring cg_invoke_command(command_context& ctx)
1119 std::wstringstream replyString;
1120 replyString << L"201 CG OK\r\n";
1121 int layer = get_and_validate_layer(ctx.parameters.at(0));
1122 auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1123 replyString << result << L"\r\n";
1125 return replyString.str();
1128 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1130 sink.short_description(L"Get information about a running template or the template host.");
1131 sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1132 sink.para()->text(L"Retrieves information about the template on the specified layer.");
1133 sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1136 std::wstring cg_info_command(command_context& ctx)
1138 std::wstringstream replyString;
1139 replyString << L"201 CG OK\r\n";
1141 if (ctx.parameters.empty())
1143 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1144 replyString << info << L"\r\n";
1148 int layer = get_and_validate_layer(ctx.parameters.at(0));
1149 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1151 replyString << desc << L"\r\n";
1154 return replyString.str();
1159 core::frame_transform get_current_transform(command_context& ctx)
1161 return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1164 template<typename Func>
1165 std::wstring reply_value(command_context& ctx, const Func& extractor)
1167 auto value = extractor(get_current_transform(ctx));
1169 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1172 class transforms_applier
1174 static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1176 std::vector<stage::transform_tuple_t> transforms_;
1177 command_context& ctx_;
1180 transforms_applier(command_context& ctx)
1183 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1186 ctx.parameters.pop_back();
1189 void add(stage::transform_tuple_t&& transform)
1191 transforms_.push_back(std::move(transform));
1194 void commit_deferred()
1196 auto& transforms = deferred_transforms_[ctx_.channel_index];
1197 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1205 auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1206 defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1209 ctx_.channel.channel->stage().apply_transforms(transforms_);
1212 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1214 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1216 sink.short_description(L"Let a layer act as alpha for the one obove.");
1217 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1219 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1220 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1221 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1222 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1223 ->text(L"instead it will be used as the key for the layer above.");
1224 sink.para()->text(L"Examples:");
1225 sink.example(L">> MIXER 1-0 KEYER 1");
1227 L">> MIXER 1-0 KEYER\n"
1228 L"<< 201 MIXER OK\n"
1229 L"<< 1", L"to retrieve the current state");
1232 std::wstring mixer_keyer_command(command_context& ctx)
1234 if (ctx.parameters.empty())
1235 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1237 transforms_applier transforms(ctx);
1238 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1239 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1241 transform.image_transform.is_key = value;
1246 return L"202 MIXER OK\r\n";
1249 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1251 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1253 sink.short_description(L"Enable chroma keying on a layer.");
1254 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1256 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1257 sink.para()->text(L"Examples:");
1258 sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1259 sink.example(L">> MIXER 1-1 CHROMA none");
1261 L">> MIXER 1-1 BLEND\n"
1262 L"<< 201 MIXER OK\n"
1263 L"<< SCREEN", L"for getting the current blend mode");
1266 std::wstring mixer_chroma_command(command_context& ctx)
1268 if (ctx.parameters.empty())
1270 auto chroma = get_current_transform(ctx).image_transform.chroma;
1271 return L"201 MIXER OK\r\n"
1272 + core::get_chroma_mode(chroma.key) + L" "
1273 + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1274 + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1275 + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1278 transforms_applier transforms(ctx);
1279 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1280 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1282 core::chroma chroma;
1283 chroma.key = get_chroma_mode(ctx.parameters.at(0));
1285 if (chroma.key != core::chroma::type::none)
1287 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1288 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1289 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1292 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1294 transform.image_transform.chroma = chroma;
1296 }, duration, tween));
1299 return L"202 MIXER OK\r\n";
1302 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1304 sink.short_description(L"Set the blend mode for a layer.");
1305 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1307 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1308 ->text(L"If no argument is given the current blend mode is returned.");
1310 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1311 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1312 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1313 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1314 sink.para()->text(L"Examples:");
1315 sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1317 L">> MIXER 1-1 BLEND\n"
1318 L"<< 201 MIXER OK\n"
1319 L"<< SCREEN", L"for getting the current blend mode");
1320 sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1323 std::wstring mixer_blend_command(command_context& ctx)
1325 if (ctx.parameters.empty())
1326 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1328 transforms_applier transforms(ctx);
1329 auto value = get_blend_mode(ctx.parameters.at(0));
1330 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1332 transform.image_transform.blend_mode = value;
1337 return L"202 MIXER OK\r\n";
1340 template<typename Getter, typename Setter>
1341 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1343 if (ctx.parameters.empty())
1344 return reply_value(ctx, getter);
1346 transforms_applier transforms(ctx);
1347 double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1348 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1349 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1351 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1353 setter(transform, value);
1355 }, duration, tween));
1358 return L"202 MIXER OK\r\n";
1361 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1363 sink.short_description(L"Change the opacity of a layer.");
1364 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1365 sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1366 sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1367 sink.para()->text(L"Examples:");
1368 sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1370 L">> MIXER 1-0 OPACITY\n"
1371 L"<< 201 MIXER OK\n"
1372 L"<< 0.5", L"to retrieve the current opacity");
1375 std::wstring mixer_opacity_command(command_context& ctx)
1377 return single_double_animatable_mixer_command(
1379 [](const frame_transform& t) { return t.image_transform.opacity; },
1380 [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1383 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1385 sink.short_description(L"Change the brightness of a layer.");
1386 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1387 sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1388 sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1389 sink.para()->text(L"Examples:");
1390 sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1392 L">> MIXER 1-0 BRIGHTNESS\n"
1393 L"<< 201 MIXER OK\n"
1394 L"0.5", L"to retrieve the current brightness");
1397 std::wstring mixer_brightness_command(command_context& ctx)
1399 return single_double_animatable_mixer_command(
1401 [](const frame_transform& t) { return t.image_transform.brightness; },
1402 [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1405 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1407 sink.short_description(L"Change the saturation of a layer.");
1408 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1409 sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1410 sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1411 sink.para()->text(L"Examples:");
1412 sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1414 L">> MIXER 1-0 SATURATION\n"
1415 L"<< 201 MIXER OK\n"
1416 L"<< 0.5", L"to retrieve the current saturation");
1419 std::wstring mixer_saturation_command(command_context& ctx)
1421 return single_double_animatable_mixer_command(
1423 [](const frame_transform& t) { return t.image_transform.saturation; },
1424 [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1427 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1429 sink.short_description(L"Change the contrast of a layer.");
1430 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1431 sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1432 sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1433 sink.para()->text(L"Examples:");
1434 sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1436 L">> MIXER 1-0 CONTRAST\n"
1437 L"<< 201 MIXER OK\n"
1438 L"<< 0.5", L"to retrieve the current contrast");
1441 std::wstring mixer_contrast_command(command_context& ctx)
1443 return single_double_animatable_mixer_command(
1445 [](const frame_transform& t) { return t.image_transform.contrast; },
1446 [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1449 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1451 sink.short_description(L"Adjust the video levels of a layer.");
1452 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);
1454 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1456 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1457 ->item(L"gamma", L"Adjusts the gamma of the image.")
1458 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1459 sink.para()->text(L"Examples:");
1460 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");
1461 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");
1462 sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1464 L">> MIXER 1-10 LEVELS\n"
1465 L"<< 201 MIXER OK\n"
1466 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1469 std::wstring mixer_levels_command(command_context& ctx)
1471 if (ctx.parameters.empty())
1473 auto levels = get_current_transform(ctx).image_transform.levels;
1474 return L"201 MIXER OK\r\n"
1475 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1476 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1477 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1478 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1479 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1482 transforms_applier transforms(ctx);
1484 value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1485 value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1486 value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1487 value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1488 value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1489 int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1490 std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1492 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1494 transform.image_transform.levels = value;
1496 }, duration, tween));
1499 return L"202 MIXER OK\r\n";
1502 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1504 sink.short_description(L"Change the fill position and scale of a layer.");
1505 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1507 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1508 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1509 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1510 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1512 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1513 ->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.");
1514 sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1516 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1517 ->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, ")
1518 ->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");
1520 ->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.")
1521 ->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.")
1522 ->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.")
1523 ->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.");
1524 sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1525 sink.para()->text(L"Examples:");
1526 sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1528 L">> MIXER 1-0 FILL\n"
1529 L"<< 201 MIXER OK\n"
1530 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1533 std::wstring mixer_fill_command(command_context& ctx)
1535 if (ctx.parameters.empty())
1537 auto transform = get_current_transform(ctx).image_transform;
1538 auto translation = transform.fill_translation;
1539 auto scale = transform.fill_scale;
1540 return L"201 MIXER OK\r\n"
1541 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1542 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1543 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1544 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1547 transforms_applier transforms(ctx);
1548 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1549 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1550 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1551 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1552 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1553 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1555 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1557 transform.image_transform.fill_translation[0] = x;
1558 transform.image_transform.fill_translation[1] = y;
1559 transform.image_transform.fill_scale[0] = x_s;
1560 transform.image_transform.fill_scale[1] = y_s;
1562 }, duration, tween));
1565 return L"202 MIXER OK\r\n";
1568 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1570 sink.short_description(L"Change the clipping viewport of a layer.");
1571 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1573 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1574 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1575 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1577 ->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.")
1578 ->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.")
1579 ->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.")
1580 ->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.");
1581 sink.para()->text(L"Examples:");
1582 sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1584 L">> MIXER 1-0 CLIP\n"
1585 L"<< 201 MIXER OK\n"
1586 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1589 std::wstring mixer_clip_command(command_context& ctx)
1591 if (ctx.parameters.empty())
1593 auto transform = get_current_transform(ctx).image_transform;
1594 auto translation = transform.clip_translation;
1595 auto scale = transform.clip_scale;
1597 return L"201 MIXER OK\r\n"
1598 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1599 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1600 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1601 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1604 transforms_applier transforms(ctx);
1605 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1606 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1607 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1608 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1609 double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1610 double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1612 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1614 transform.image_transform.clip_translation[0] = x;
1615 transform.image_transform.clip_translation[1] = y;
1616 transform.image_transform.clip_scale[0] = x_s;
1617 transform.image_transform.clip_scale[1] = y_s;
1619 }, duration, tween));
1622 return L"202 MIXER OK\r\n";
1625 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1627 sink.short_description(L"Change the anchor point of a layer.");
1628 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1629 sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1631 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1632 ->text(L" will be done from.");
1634 ->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.")
1635 ->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.");
1636 sink.para()->text(L"Examples:");
1637 sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1639 L">> MIXER 1-10 ANCHOR\n"
1640 L"<< 201 MIXER OK\n"
1641 L"<< 0.5 0.6", L"gets the anchor point");
1644 std::wstring mixer_anchor_command(command_context& ctx)
1646 if (ctx.parameters.empty())
1648 auto transform = get_current_transform(ctx).image_transform;
1649 auto anchor = transform.anchor;
1650 return L"201 MIXER OK\r\n"
1651 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1652 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1655 transforms_applier transforms(ctx);
1656 int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1657 std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1658 double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1659 double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1661 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1663 transform.image_transform.anchor[0] = x;
1664 transform.image_transform.anchor[1] = y;
1666 }, duration, tween));
1669 return L"202 MIXER OK\r\n";
1672 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1674 sink.short_description(L"Crop a layer.");
1675 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);
1677 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1678 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1679 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1681 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1682 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1683 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1684 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1685 sink.para()->text(L"Examples:");
1686 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");
1688 L">> MIXER 1-0 CROP\n"
1689 L"<< 201 MIXER OK\n"
1690 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1693 std::wstring mixer_crop_command(command_context& ctx)
1695 if (ctx.parameters.empty())
1697 auto crop = get_current_transform(ctx).image_transform.crop;
1698 return L"201 MIXER OK\r\n"
1699 + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1700 + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1701 + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1702 + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1705 transforms_applier transforms(ctx);
1706 int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1707 std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1708 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1709 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1710 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1711 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1713 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1715 transform.image_transform.crop.ul[0] = ul_x;
1716 transform.image_transform.crop.ul[1] = ul_y;
1717 transform.image_transform.crop.lr[0] = lr_x;
1718 transform.image_transform.crop.lr[1] = lr_y;
1720 }, duration, tween));
1723 return L"202 MIXER OK\r\n";
1726 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1728 sink.short_description(L"Rotate a layer.");
1729 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1731 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1732 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1733 sink.para()->text(L"Examples:");
1734 sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1736 L">> MIXER 1-0 ROTATION\n"
1737 L"<< 201 MIXER OK\n"
1738 L"<< 45", L"to retrieve the current angle");
1741 std::wstring mixer_rotation_command(command_context& ctx)
1743 static const double PI = 3.141592653589793;
1745 return single_double_animatable_mixer_command(
1747 [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1748 [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1751 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1753 sink.short_description(L"Adjust the perspective transform of a layer.");
1754 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);
1756 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1758 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1759 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1760 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1761 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1762 sink.para()->text(L"Examples:");
1763 sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1765 L">> MIXER 1-10 PERSPECTIVE\n"
1766 L"<< 201 MIXER OK\n"
1767 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1770 std::wstring mixer_perspective_command(command_context& ctx)
1772 if (ctx.parameters.empty())
1774 auto perspective = get_current_transform(ctx).image_transform.perspective;
1777 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1778 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1779 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1780 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1781 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1782 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1783 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1784 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1787 transforms_applier transforms(ctx);
1788 int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1789 std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1790 double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1791 double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1792 double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1793 double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1794 double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1795 double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1796 double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1797 double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1799 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1801 transform.image_transform.perspective.ul[0] = ul_x;
1802 transform.image_transform.perspective.ul[1] = ul_y;
1803 transform.image_transform.perspective.ur[0] = ur_x;
1804 transform.image_transform.perspective.ur[1] = ur_y;
1805 transform.image_transform.perspective.lr[0] = lr_x;
1806 transform.image_transform.perspective.lr[1] = lr_y;
1807 transform.image_transform.perspective.ll[0] = ll_x;
1808 transform.image_transform.perspective.ll[1] = ll_y;
1810 }, duration, tween));
1813 return L"202 MIXER OK\r\n";
1816 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1818 sink.short_description(L"Enable or disable mipmapping for a layer.");
1819 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1821 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1822 ->text(L"If no argument is given the current state is returned.");
1823 sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1824 sink.para()->text(L"Examples:");
1825 sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1827 L">> MIXER 1-10 MIPMAP\n"
1828 L"<< 201 MIXER OK\n"
1829 L"<< 1", L"for getting the current state");
1832 std::wstring mixer_mipmap_command(command_context& ctx)
1834 if (ctx.parameters.empty())
1835 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1837 transforms_applier transforms(ctx);
1838 bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1839 transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1841 transform.image_transform.use_mipmap = value;
1846 return L"202 MIXER OK\r\n";
1849 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1851 sink.short_description(L"Change the volume of a layer.");
1852 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1853 sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1854 sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1855 sink.para()->text(L"Examples:");
1856 sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1857 sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1859 L">> MIXER 1-0 VOLUME\n"
1860 L"<< 201 MIXER OK\n"
1861 L"<< 0.8", L"to retrieve the current volume");
1864 std::wstring mixer_volume_command(command_context& ctx)
1866 return single_double_animatable_mixer_command(
1868 [](const frame_transform& t) { return t.audio_transform.volume; },
1869 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1872 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1874 sink.short_description(L"Change the volume of an entire channel.");
1875 sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1876 sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1877 sink.para()->text(L"Examples:");
1878 sink.example(L">> MIXER 1 MASTERVOLUME 0");
1879 sink.example(L">> MIXER 1 MASTERVOLUME 1");
1880 sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1883 std::wstring mixer_mastervolume_command(command_context& ctx)
1885 if (ctx.parameters.empty())
1887 auto volume = ctx.channel.channel->mixer().get_master_volume();
1888 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1891 float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1892 ctx.channel.channel->mixer().set_master_volume(master_volume);
1894 return L"202 MIXER OK\r\n";
1897 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1899 sink.short_description(L"Turn straight alpha output on or off for a channel.");
1900 sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1901 sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1902 sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1903 sink.para()->text(L"Examples:");
1904 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1905 sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1907 L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1908 L"<< 201 MIXER OK\n"
1912 std::wstring mixer_straight_alpha_command(command_context& ctx)
1914 if (ctx.parameters.empty())
1916 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
1917 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
1920 bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
1921 ctx.channel.channel->mixer().set_straight_alpha_output(state);
1923 return L"202 MIXER OK\r\n";
1926 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1928 sink.short_description(L"Create a grid of video layers.");
1929 sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1931 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1932 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1933 sink.para()->text(L"Examples:");
1934 sink.example(L">> MIXER 1 GRID 2");
1937 std::wstring mixer_grid_command(command_context& ctx)
1939 transforms_applier transforms(ctx);
1940 int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1941 std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1942 int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1943 double delta = 1.0 / static_cast<double>(n);
1944 for (int x = 0; x < n; ++x)
1946 for (int y = 0; y < n; ++y)
1948 int index = x + y*n + 1;
1949 transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1951 transform.image_transform.fill_translation[0] = x*delta;
1952 transform.image_transform.fill_translation[1] = y*delta;
1953 transform.image_transform.fill_scale[0] = delta;
1954 transform.image_transform.fill_scale[1] = delta;
1955 transform.image_transform.clip_translation[0] = x*delta;
1956 transform.image_transform.clip_translation[1] = y*delta;
1957 transform.image_transform.clip_scale[0] = delta;
1958 transform.image_transform.clip_scale[1] = delta;
1960 }, duration, tween));
1965 return L"202 MIXER OK\r\n";
1968 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1970 sink.short_description(L"Commit all deferred mixer transforms.");
1971 sink.syntax(L"MIXER [video_channel:int] COMMIT");
1972 sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1973 sink.para()->text(L"Examples:");
1975 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1976 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1977 L">> MIXER 1 COMMIT");
1980 std::wstring mixer_commit_command(command_context& ctx)
1982 transforms_applier transforms(ctx);
1983 transforms.commit_deferred();
1985 return L"202 MIXER OK\r\n";
1988 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1990 sink.short_description(L"Clear all transformations on a channel or layer.");
1991 sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1992 sink.para()->text(L"Clears all transformations on a channel or layer.");
1993 sink.para()->text(L"Examples:");
1994 sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1995 sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1998 std::wstring mixer_clear_command(command_context& ctx)
2000 int layer = ctx.layer_id;
2003 ctx.channel.channel->stage().clear_transforms();
2005 ctx.channel.channel->stage().clear_transforms(layer);
2007 return L"202 MIXER OK\r\n";
2010 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2012 sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2013 sink.syntax(L"CHANNEL_GRID");
2014 sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2016 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2017 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2020 std::wstring channel_grid_command(command_context& ctx)
2023 auto self = ctx.channels.back();
2025 core::diagnostics::scoped_call_context save;
2026 core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2028 std::vector<std::wstring> params;
2029 params.push_back(L"SCREEN");
2030 params.push_back(L"0");
2031 params.push_back(L"NAME");
2032 params.push_back(L"Channel Grid Window");
2033 auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
2035 self.channel->output().add(screen);
2037 for (auto& channel : ctx.channels)
2039 if (channel.channel != self.channel)
2041 core::diagnostics::call_context::for_thread().layer = index;
2042 auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2043 self.channel->stage().load(index, producer, false);
2044 self.channel->stage().play(index);
2049 auto num_channels = ctx.channels.size() - 1;
2050 int square_side_length = std::ceil(std::sqrt(num_channels));
2052 ctx.channel_index = self.channel->index();
2054 ctx.parameters.clear();
2055 ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2056 mixer_grid_command(ctx);
2058 return L"202 CHANNEL_GRID OK\r\n";
2061 // Thumbnail Commands
2063 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2065 sink.short_description(L"List all thumbnails.");
2066 sink.syntax(L"THUMBNAIL LIST");
2067 sink.para()->text(L"Lists all thumbnails.");
2068 sink.para()->text(L"Examples:");
2070 L">> THUMBNAIL LIST\n"
2071 L"<< 200 THUMBNAIL LIST OK\n"
2072 L"<< \"AMB\" 20130301T124409 1149\n"
2073 L"<< \"foo/bar\" 20130523T234001 244");
2076 std::wstring thumbnail_list_command(command_context& ctx)
2078 std::wstringstream replyString;
2079 replyString << L"200 THUMBNAIL LIST OK\r\n";
2081 for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2083 if (boost::filesystem::is_regular_file(itr->path()))
2085 if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2088 auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
2090 auto str = relativePath.replace_extension(L"").generic_wstring();
2091 if (str[0] == '\\' || str[0] == '/')
2092 str = std::wstring(str.begin() + 1, str.end());
2094 auto mtime = boost::filesystem::last_write_time(itr->path());
2095 auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2096 auto file_size = boost::filesystem::file_size(itr->path());
2098 replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2102 replyString << L"\r\n";
2104 return boost::to_upper_copy(replyString.str());
2107 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2109 sink.short_description(L"Retrieve a thumbnail.");
2110 sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2111 sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2112 sink.para()->text(L"Examples:");
2114 L">> THUMBNAIL RETRIEVE foo/bar\n"
2115 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2116 L"<< ...base64 data...");
2119 std::wstring thumbnail_retrieve_command(command_context& ctx)
2121 std::wstring filename = env::thumbnails_folder();
2122 filename.append(ctx.parameters.at(0));
2123 filename.append(L".png");
2125 std::wstring file_contents;
2127 auto found_file = find_case_insensitive(filename);
2130 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2132 if (file_contents.empty())
2133 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2135 std::wstringstream reply;
2137 reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2138 reply << file_contents;
2143 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2145 sink.short_description(L"Regenerate a thumbnail.");
2146 sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2147 sink.para()->text(L"Regenerates a thumbnail.");
2150 std::wstring thumbnail_generate_command(command_context& ctx)
2154 ctx.thumb_gen->generate(ctx.parameters.at(0));
2155 return L"202 THUMBNAIL GENERATE OK\r\n";
2158 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2161 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2163 sink.short_description(L"Regenerate all thumbnails.");
2164 sink.syntax(L"THUMBNAIL GENERATE_ALL");
2165 sink.para()->text(L"Regenerates all thumbnails.");
2168 std::wstring thumbnail_generateall_command(command_context& ctx)
2172 ctx.thumb_gen->generate_all();
2173 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2176 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2181 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2183 sink.short_description(L"Get information about a media file.");
2184 sink.syntax(L"CINF [filename:string]");
2185 sink.para()->text(L"Returns information about a media file.");
2188 std::wstring cinf_command(command_context& ctx)
2191 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2193 auto path = itr->path();
2194 auto file = path.replace_extension(L"").filename().wstring();
2195 if (boost::iequals(file, ctx.parameters.at(0)))
2196 info += MediaInfo(itr->path(), ctx.media_info_repo);
2200 CASPAR_THROW_EXCEPTION(file_not_found());
2202 std::wstringstream replyString;
2203 replyString << L"200 CINF OK\r\n";
2204 replyString << info << "\r\n";
2206 return replyString.str();
2209 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2211 sink.short_description(L"List all media files.");
2212 sink.syntax(L"CLS");
2214 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2215 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2218 std::wstring cls_command(command_context& ctx)
2220 std::wstringstream replyString;
2221 replyString << L"200 CLS OK\r\n";
2222 replyString << ListMedia(ctx.media_info_repo);
2223 replyString << L"\r\n";
2224 return boost::to_upper_copy(replyString.str());
2227 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2229 sink.short_description(L"List all templates.");
2230 sink.syntax(L"TLS");
2232 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2233 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2236 std::wstring tls_command(command_context& ctx)
2238 std::wstringstream replyString;
2239 replyString << L"200 TLS OK\r\n";
2241 replyString << ListTemplates(ctx.cg_registry);
2242 replyString << L"\r\n";
2244 return replyString.str();
2247 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2249 sink.short_description(L"Get version information.");
2250 sink.syntax(L"VERSION {[component:string]}");
2251 sink.para()->text(L"Returns the version of specified component.");
2252 sink.para()->text(L"Examples:");
2255 L"<< 201 VERSION OK\n"
2256 L"<< 2.1.0.f207a33 STABLE");
2258 L">> VERSION SERVER\n"
2259 L"<< 201 VERSION OK\n"
2260 L"<< 2.1.0.f207a33 STABLE");
2262 L">> VERSION FLASH\n"
2263 L"<< 201 VERSION OK\n"
2267 std::wstring version_command(command_context& ctx)
2269 if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2271 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2273 return L"201 VERSION OK\r\n" + version + L"\r\n";
2276 return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2279 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2281 sink.short_description(L"Get a list of the available channels.");
2282 sink.syntax(L"INFO");
2283 sink.para()->text(L"Retrieves a list of the available channels.");
2287 L"<< 1 720p5000 PLAYING\n"
2288 L"<< 2 PAL PLAYING");
2291 std::wstring info_command(command_context& ctx)
2293 std::wstringstream replyString;
2294 // This is needed for backwards compatibility with old clients
2295 replyString << L"200 INFO OK\r\n";
2296 for (size_t n = 0; n < ctx.channels.size(); ++n)
2297 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2298 replyString << L"\r\n";
2299 return replyString.str();
2302 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2304 std::wstringstream replyString;
2306 if (command.empty())
2307 replyString << L"201 INFO OK\r\n";
2309 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2311 boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2312 boost::property_tree::xml_parser::write_xml(replyString, info, w);
2313 replyString << L"\r\n";
2314 return replyString.str();
2317 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2319 sink.short_description(L"Get information about a channel or a layer.");
2320 sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2321 sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2322 sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2325 std::wstring info_channel_command(command_context& ctx)
2327 boost::property_tree::wptree info;
2328 int layer = ctx.layer_index(std::numeric_limits<int>::min());
2330 if (layer == std::numeric_limits<int>::min())
2332 info.add_child(L"channel", ctx.channel.channel->info())
2333 .add(L"index", ctx.channel_index);
2337 if (ctx.parameters.size() >= 1)
2339 if (boost::iequals(ctx.parameters.at(0), L"B"))
2340 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2342 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2346 info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2350 return create_info_xml_reply(info);
2353 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2355 sink.short_description(L"Get information about a template.");
2356 sink.syntax(L"INFO TEMPLATE [template:string]");
2357 sink.para()->text(L"Gets information about the specified template.");
2360 std::wstring info_template_command(command_context& ctx)
2362 auto filename = ctx.parameters.at(0);
2364 std::wstringstream str;
2365 str << u16(ctx.cg_registry->read_meta_info(filename));
2366 boost::property_tree::wptree info;
2367 boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2369 return create_info_xml_reply(info, L"TEMPLATE");
2372 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2374 sink.short_description(L"Get the contents of the configuration used.");
2375 sink.syntax(L"INFO CONFIG");
2376 sink.para()->text(L"Gets the contents of the configuration used.");
2379 std::wstring info_config_command(command_context& ctx)
2381 return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2384 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2386 sink.short_description(L"Get information about the paths used.");
2387 sink.syntax(L"INFO PATHS");
2388 sink.para()->text(L"Gets information about the paths used.");
2391 std::wstring info_paths_command(command_context& ctx)
2393 boost::property_tree::wptree info;
2394 info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2395 info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2397 return create_info_xml_reply(info, L"PATHS");
2400 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2402 sink.short_description(L"Get system information.");
2403 sink.syntax(L"INFO SYSTEM");
2404 sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2407 std::wstring info_system_command(command_context& ctx)
2409 boost::property_tree::wptree info;
2411 info.add(L"system.name", caspar::system_product_name());
2412 info.add(L"system.os.description", caspar::os_description());
2413 info.add(L"system.cpu", caspar::cpu_info());
2415 ctx.system_info_repo->fill_information(info);
2417 return create_info_xml_reply(info, L"SYSTEM");
2420 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2422 sink.short_description(L"Get detailed information about all channels.");
2423 sink.syntax(L"INFO SERVER");
2424 sink.para()->text(L"Gets detailed information about all channels.");
2427 std::wstring info_server_command(command_context& ctx)
2429 boost::property_tree::wptree info;
2432 for (auto& channel : ctx.channels)
2433 info.add_child(L"channels.channel", channel.channel->info())
2434 .add(L"index", ++index);
2436 return create_info_xml_reply(info, L"SERVER");
2439 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2441 sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2442 sink.syntax(L"INFO QUEUES");
2443 sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2446 std::wstring info_queues_command(command_context& ctx)
2448 return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2451 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2453 sink.short_description(L"Lists all known threads in the server.");
2454 sink.syntax(L"INFO THREADS");
2455 sink.para()->text(L"Lists all known threads in the server.");
2458 std::wstring info_threads_command(command_context& ctx)
2460 std::wstringstream replyString;
2461 replyString << L"200 INFO THREADS OK\r\n";
2463 for (auto& thread : get_thread_infos())
2465 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2468 replyString << L"\r\n";
2469 return replyString.str();
2472 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2474 sink.short_description(L"Get the current delay on a channel or a layer.");
2475 sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2476 sink.para()->text(L"Get the current delay on the specified channel or layer.");
2479 std::wstring info_delay_command(command_context& ctx)
2481 boost::property_tree::wptree info;
2482 auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2484 if (layer == std::numeric_limits<int>::min())
2485 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2487 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2488 .add(L"index", layer);
2490 return create_info_xml_reply(info, L"DELAY");
2493 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2495 sink.short_description(L"Open the diagnostics window.");
2496 sink.syntax(L"DIAG");
2497 sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2500 std::wstring diag_command(command_context& ctx)
2502 core::diagnostics::osd::show_graphs(true);
2504 return L"202 DIAG OK\r\n";
2507 static const int WIDTH = 80;
2509 struct max_width_sink : public core::help_sink
2511 std::size_t max_width = 0;
2513 void begin_item(const std::wstring& name) override
2515 max_width = std::max(name.length(), max_width);
2519 struct short_description_sink : public core::help_sink
2522 std::wstringstream& out;
2524 short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2526 void begin_item(const std::wstring& name) override
2528 out << std::left << std::setw(width + 1) << name;
2531 void short_description(const std::wstring& short_description) override
2533 out << short_description << L"\r\n";
2537 struct simple_paragraph_builder : core::paragraph_builder
2539 std::wostringstream out;
2540 std::wstringstream& commit_to;
2542 simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2543 ~simple_paragraph_builder()
2545 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2547 spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2549 out << std::move(text);
2550 return shared_from_this();
2552 spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2553 spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2554 spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
2557 struct simple_definition_list_builder : core::definition_list_builder
2559 std::wstringstream& out;
2561 simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2562 ~simple_definition_list_builder()
2567 spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2569 out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
2570 out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
2571 return shared_from_this();
2575 struct long_description_sink : public core::help_sink
2577 std::wstringstream& out;
2579 long_description_sink(std::wstringstream& out) : out(out) { }
2581 void syntax(const std::wstring& syntax) override
2583 out << L"Syntax:\n";
2584 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
2587 spl::shared_ptr<core::paragraph_builder> para() override
2589 return spl::make_shared<simple_paragraph_builder>(out);
2592 spl::shared_ptr<core::definition_list_builder> definitions() override
2594 return spl::make_shared<simple_definition_list_builder>(out);
2597 void example(const std::wstring& code, const std::wstring& caption) override
2599 out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
2601 if (!caption.empty())
2602 out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
2607 void begin_item(const std::wstring& name) override
2609 out << name << L"\n\n";
2613 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2615 std::wstringstream result;
2616 result << L"200 " << help_command << L" OK\r\n";
2617 max_width_sink width;
2618 ctx.help_repo->help(tags, width);
2619 short_description_sink sink(width.max_width, result);
2620 sink.width = width.max_width;
2621 ctx.help_repo->help(tags, sink);
2623 return result.str();
2626 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2628 std::wstringstream result;
2629 result << L"201 " << help_command << L" OK\r\n";
2630 auto joined = boost::join(ctx.parameters, L" ");
2631 long_description_sink sink(result);
2632 ctx.help_repo->help(tags, joined, sink);
2634 return result.str();
2637 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2639 sink.short_description(L"Show online help for AMCP commands.");
2640 sink.syntax(LR"(HELP {[command:string]})");
2641 sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2642 sink.example(L">> HELP", L"Shows a list of commands.");
2643 sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2646 std::wstring help_command(command_context& ctx)
2648 if (ctx.parameters.size() == 0)
2649 return create_help_list(L"HELP", ctx, { L"AMCP" });
2651 return create_help_details(L"HELP", ctx, { L"AMCP" });
2654 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2656 sink.short_description(L"Show online help for producers.");
2657 sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2658 sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2659 sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2660 sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2663 std::wstring help_producer_command(command_context& ctx)
2665 if (ctx.parameters.size() == 0)
2666 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2668 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2671 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2673 sink.short_description(L"Show online help for consumers.");
2674 sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2675 sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2676 sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2677 sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2680 std::wstring help_consumer_command(command_context& ctx)
2682 if (ctx.parameters.size() == 0)
2683 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2685 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2688 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2690 sink.short_description(L"Disconnect the session.");
2691 sink.syntax(L"BYE");
2693 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2694 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2697 std::wstring bye_command(command_context& ctx)
2699 ctx.client->disconnect();
2703 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2705 sink.short_description(L"Shutdown the server.");
2706 sink.syntax(L"KILL");
2707 sink.para()->text(L"Shuts the server down.");
2710 std::wstring kill_command(command_context& ctx)
2712 ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
2713 return L"202 KILL OK\r\n";
2716 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2718 sink.short_description(L"Shutdown the server with restart exit code.");
2719 sink.syntax(L"RESTART");
2721 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2722 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2725 std::wstring restart_command(command_context& ctx)
2727 ctx.shutdown_server_now.set_value(true); //true for attempting to restart
2728 return L"202 RESTART OK\r\n";
2731 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2733 sink.short_description(L"Lock or unlock access to a channel.");
2734 sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2735 sink.para()->text(L"Allows for exclusive access to a channel.");
2736 sink.para()->text(L"Examples:");
2737 sink.example(L"LOCK 1 ACQUIRE secret");
2738 sink.example(L"LOCK 1 RELEASE");
2739 sink.example(L"LOCK 1 CLEAR");
2742 std::wstring lock_command(command_context& ctx)
2744 int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2745 auto lock = ctx.channels.at(channel_index).lock;
2746 auto command = boost::to_upper_copy(ctx.parameters.at(1));
2748 if (command == L"ACQUIRE")
2750 std::wstring lock_phrase = ctx.parameters.at(2);
2752 //TODO: read options
2754 //just lock one channel
2755 if (!lock->try_lock(lock_phrase, ctx.client))
2756 return L"503 LOCK ACQUIRE FAILED\r\n";
2758 return L"202 LOCK ACQUIRE OK\r\n";
2760 else if (command == L"RELEASE")
2762 lock->release_lock(ctx.client);
2763 return L"202 LOCK RELEASE OK\r\n";
2765 else if (command == L"CLEAR")
2767 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2768 std::wstring client_override_phrase;
2770 if (!override_phrase.empty())
2771 client_override_phrase = ctx.parameters.at(2);
2773 //just clear one channel
2774 if (client_override_phrase != override_phrase)
2775 return L"503 LOCK CLEAR FAILED\r\n";
2777 lock->clear_locks();
2779 return L"202 LOCK CLEAR OK\r\n";
2782 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2785 void register_commands(amcp_command_repository& repo)
2787 repo.register_channel_command( L"Basic Commands", L"LOADBG", loadbg_describer, loadbg_command, 1);
2788 repo.register_channel_command( L"Basic Commands", L"LOAD", load_describer, load_command, 1);
2789 repo.register_channel_command( L"Basic Commands", L"PLAY", play_describer, play_command, 0);
2790 repo.register_channel_command( L"Basic Commands", L"PAUSE", pause_describer, pause_command, 0);
2791 repo.register_channel_command( L"Basic Commands", L"RESUME", resume_describer, resume_command, 0);
2792 repo.register_channel_command( L"Basic Commands", L"STOP", stop_describer, stop_command, 0);
2793 repo.register_channel_command( L"Basic Commands", L"CLEAR", clear_describer, clear_command, 0);
2794 repo.register_channel_command( L"Basic Commands", L"CALL", call_describer, call_command, 1);
2795 repo.register_channel_command( L"Basic Commands", L"SWAP", swap_describer, swap_command, 1);
2796 repo.register_channel_command( L"Basic Commands", L"ADD", add_describer, add_command, 1);
2797 repo.register_channel_command( L"Basic Commands", L"REMOVE", remove_describer, remove_command, 0);
2798 repo.register_channel_command( L"Basic Commands", L"PRINT", print_describer, print_command, 0);
2799 repo.register_command( L"Basic Commands", L"LOG LEVEL", log_level_describer, log_level_command, 1);
2800 repo.register_channel_command( L"Basic Commands", L"SET", set_describer, set_command, 2);
2801 repo.register_command( L"Basic Commands", L"LOCK", lock_describer, lock_command, 2);
2803 repo.register_command( L"Data Commands", L"DATA STORE", data_store_describer, data_store_command, 2);
2804 repo.register_command( L"Data Commands", L"DATA RETRIEVE", data_retrieve_describer, data_retrieve_command, 1);
2805 repo.register_command( L"Data Commands", L"DATA LIST", data_list_describer, data_list_command, 0);
2806 repo.register_command( L"Data Commands", L"DATA REMOVE", data_remove_describer, data_remove_command, 1);
2808 repo.register_channel_command( L"Template Commands", L"CG ADD", cg_add_describer, cg_add_command, 3);
2809 repo.register_channel_command( L"Template Commands", L"CG PLAY", cg_play_describer, cg_play_command, 1);
2810 repo.register_channel_command( L"Template Commands", L"CG STOP", cg_stop_describer, cg_stop_command, 1);
2811 repo.register_channel_command( L"Template Commands", L"CG NEXT", cg_next_describer, cg_next_command, 1);
2812 repo.register_channel_command( L"Template Commands", L"CG REMOVE", cg_remove_describer, cg_remove_command, 1);
2813 repo.register_channel_command( L"Template Commands", L"CG CLEAR", cg_clear_describer, cg_clear_command, 0);
2814 repo.register_channel_command( L"Template Commands", L"CG UPDATE", cg_update_describer, cg_update_command, 2);
2815 repo.register_channel_command( L"Template Commands", L"CG INVOKE", cg_invoke_describer, cg_invoke_command, 2);
2816 repo.register_channel_command( L"Template Commands", L"CG INFO", cg_info_describer, cg_info_command, 0);
2818 repo.register_channel_command( L"Mixer Commands", L"MIXER KEYER", mixer_keyer_describer, mixer_keyer_command, 0);
2819 repo.register_channel_command( L"Mixer Commands", L"MIXER CHROMA", mixer_chroma_describer, mixer_chroma_command, 0);
2820 repo.register_channel_command( L"Mixer Commands", L"MIXER BLEND", mixer_blend_describer, mixer_blend_command, 0);
2821 repo.register_channel_command( L"Mixer Commands", L"MIXER OPACITY", mixer_opacity_describer, mixer_opacity_command, 0);
2822 repo.register_channel_command( L"Mixer Commands", L"MIXER BRIGHTNESS", mixer_brightness_describer, mixer_brightness_command, 0);
2823 repo.register_channel_command( L"Mixer Commands", L"MIXER SATURATION", mixer_saturation_describer, mixer_saturation_command, 0);
2824 repo.register_channel_command( L"Mixer Commands", L"MIXER CONTRAST", mixer_contrast_describer, mixer_contrast_command, 0);
2825 repo.register_channel_command( L"Mixer Commands", L"MIXER LEVELS", mixer_levels_describer, mixer_levels_command, 0);
2826 repo.register_channel_command( L"Mixer Commands", L"MIXER FILL", mixer_fill_describer, mixer_fill_command, 0);
2827 repo.register_channel_command( L"Mixer Commands", L"MIXER CLIP", mixer_clip_describer, mixer_clip_command, 0);
2828 repo.register_channel_command( L"Mixer Commands", L"MIXER ANCHOR", mixer_anchor_describer, mixer_anchor_command, 0);
2829 repo.register_channel_command( L"Mixer Commands", L"MIXER CROP", mixer_crop_describer, mixer_crop_command, 0);
2830 repo.register_channel_command( L"Mixer Commands", L"MIXER ROTATION", mixer_rotation_describer, mixer_rotation_command, 0);
2831 repo.register_channel_command( L"Mixer Commands", L"MIXER PERSPECTIVE", mixer_perspective_describer, mixer_perspective_command, 0);
2832 repo.register_channel_command( L"Mixer Commands", L"MIXER MIPMAP", mixer_mipmap_describer, mixer_mipmap_command, 0);
2833 repo.register_channel_command( L"Mixer Commands", L"MIXER VOLUME", mixer_volume_describer, mixer_volume_command, 0);
2834 repo.register_channel_command( L"Mixer Commands", L"MIXER MASTERVOLUME", mixer_mastervolume_describer, mixer_mastervolume_command, 0);
2835 repo.register_channel_command( L"Mixer Commands", L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer, mixer_straight_alpha_command, 0);
2836 repo.register_channel_command( L"Mixer Commands", L"MIXER GRID", mixer_grid_describer, mixer_grid_command, 1);
2837 repo.register_channel_command( L"Mixer Commands", L"MIXER COMMIT", mixer_commit_describer, mixer_commit_command, 0);
2838 repo.register_channel_command( L"Mixer Commands", L"MIXER CLEAR", mixer_clear_describer, mixer_clear_command, 0);
2839 repo.register_command( L"Mixer Commands", L"CHANNEL_GRID", channel_grid_describer, channel_grid_command, 0);
2841 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL LIST", thumbnail_list_describer, thumbnail_list_command, 0);
2842 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL RETRIEVE", thumbnail_retrieve_describer, thumbnail_retrieve_command, 1);
2843 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE", thumbnail_generate_describer, thumbnail_generate_command, 1);
2844 repo.register_command( L"Thumbnail Commands", L"THUMBNAIL GENERATE_ALL", thumbnail_generateall_describer, thumbnail_generateall_command, 0);
2846 repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1);
2847 repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0);
2848 repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0);
2849 repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0);
2850 repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0);
2851 repo.register_channel_command( L"Query Commands", L"INFO", info_channel_describer, info_channel_command, 0);
2852 repo.register_command( L"Query Commands", L"INFO TEMPLATE", info_template_describer, info_template_command, 1);
2853 repo.register_command( L"Query Commands", L"INFO CONFIG", info_config_describer, info_config_command, 0);
2854 repo.register_command( L"Query Commands", L"INFO PATHS", info_paths_describer, info_paths_command, 0);
2855 repo.register_command( L"Query Commands", L"INFO SYSTEM", info_system_describer, info_system_command, 0);
2856 repo.register_command( L"Query Commands", L"INFO SERVER", info_server_describer, info_server_command, 0);
2857 repo.register_command( L"Query Commands", L"INFO QUEUES", info_queues_describer, info_queues_command, 0);
2858 repo.register_command( L"Query Commands", L"INFO THREADS", info_threads_describer, info_threads_command, 0);
2859 repo.register_channel_command( L"Query Commands", L"INFO DELAY", info_delay_describer, info_delay_command, 0);
2860 repo.register_command( L"Query Commands", L"DIAG", diag_describer, diag_command, 0);
2861 repo.register_command( L"Query Commands", L"BYE", bye_describer, bye_command, 0);
2862 repo.register_command( L"Query Commands", L"KILL", kill_describer, kill_command, 0);
2863 repo.register_command( L"Query Commands", L"RESTART", restart_describer, restart_command, 0);
2864 repo.register_command( L"Query Commands", L"HELP", help_describer, help_command, 0);
2865 repo.register_command( L"Query Commands", L"HELP PRODUCER", help_producer_describer, help_producer_command, 0);
2866 repo.register_command( L"Query Commands", L"HELP CONSUMER", help_consumer_describer, help_consumer_command, 0);
2870 }} //namespace caspar