]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPCommandsImpl.cpp
a46120cc6f0996d94d5f97f25d2a552ab4a40605
[casparcg] / protocol / amcp / AMCPCommandsImpl.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 * Author: Nicklas P Andersson
20 */
21
22 #include "../StdAfx.h"
23
24 #if defined(_MSC_VER)
25 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
26 #endif
27
28 #include "AMCPCommandsImpl.h"
29
30 #include "amcp_command_repository.h"
31 #include "AMCPCommandQueue.h"
32
33 #include <common/env.h>
34
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>
41 #include <common/filesystem.h>
42
43 #include <core/producer/cg_proxy.h>
44 #include <core/producer/frame_producer.h>
45 #include <core/help/help_repository.h>
46 #include <core/help/help_sink.h>
47 #include <core/help/util.h>
48 #include <core/video_format.h>
49 #include <core/producer/transition/transition_producer.h>
50 #include <core/frame/audio_channel_layout.h>
51 #include <core/frame/frame_transform.h>
52 #include <core/producer/text/text_producer.h>
53 #include <core/producer/stage.h>
54 #include <core/producer/layer.h>
55 #include <core/mixer/mixer.h>
56 #include <core/consumer/output.h>
57 #include <core/thumbnail_generator.h>
58 #include <core/producer/media_info/media_info.h>
59 #include <core/producer/media_info/media_info_repository.h>
60 #include <core/diagnostics/call_context.h>
61 #include <core/diagnostics/osd_graph.h>
62 #include <core/system_info_provider.h>
63
64 #include <algorithm>
65 #include <locale>
66 #include <fstream>
67 #include <memory>
68 #include <cctype>
69 #include <future>
70
71 #include <boost/date_time/posix_time/posix_time.hpp>
72 #include <boost/lexical_cast.hpp>
73 #include <boost/algorithm/string.hpp>
74 #include <boost/filesystem.hpp>
75 #include <boost/filesystem/fstream.hpp>
76 #include <boost/regex.hpp>
77 #include <boost/property_tree/xml_parser.hpp>
78 #include <boost/locale.hpp>
79 #include <boost/range/adaptor/transformed.hpp>
80 #include <boost/range/algorithm/copy.hpp>
81 #include <boost/archive/iterators/base64_from_binary.hpp>
82 #include <boost/archive/iterators/insert_linebreaks.hpp>
83 #include <boost/archive/iterators/transform_width.hpp>
84
85 #include <tbb/concurrent_unordered_map.h>
86
87 /* Return codes
88
89 102 [action]                    Information that [action] has happened
90 101 [action]                    Information that [action] has happened plus one row of data
91
92 202 [command] OK                [command] has been executed
93 201 [command] OK                [command] has been executed, plus one row of data
94 200 [command] OK                [command] has been executed, plus multiple lines of data. ends with an empty line
95
96 400 ERROR                               the command could not be understood
97 401 [command] ERROR             invalid/missing channel
98 402 [command] ERROR             parameter missing
99 403 [command] ERROR             invalid parameter
100 404 [command] ERROR             file not found
101
102 500 FAILED                              internal error
103 501 [command] FAILED    internal error
104 502 [command] FAILED    could not read file
105 503 [command] FAILED    access denied
106
107 600 [command] FAILED    [command] not implemented
108 */
109
110 namespace caspar { namespace protocol { namespace amcp {
111
112 using namespace core;
113
114 std::wstring read_file_base64(const boost::filesystem::path& file)
115 {
116         using namespace boost::archive::iterators;
117
118         boost::filesystem::ifstream filestream(file, std::ios::binary);
119
120         if (!filestream)
121                 return L"";
122
123         auto length = boost::filesystem::file_size(file);
124         std::vector<char> bytes;
125         bytes.resize(length);
126         filestream.read(bytes.data(), length);
127
128         std::string result(to_base64(bytes.data(), length));
129         return std::wstring(result.begin(), result.end());
130 }
131
132 std::wstring read_utf8_file(const boost::filesystem::path& file)
133 {
134         std::wstringstream result;
135         boost::filesystem::wifstream filestream(file);
136
137         if (filestream)
138         {
139                 // Consume BOM first
140                 filestream.get();
141                 // read all data
142                 result << filestream.rdbuf();
143         }
144
145         return result.str();
146 }
147
148 std::wstring read_latin1_file(const boost::filesystem::path& file)
149 {
150         boost::locale::generator gen;
151         gen.locale_cache_enabled(true);
152         gen.categories(boost::locale::codepage_facet);
153
154         std::stringstream result_stream;
155         boost::filesystem::ifstream filestream(file);
156         filestream.imbue(gen("en_US.ISO8859-1"));
157
158         if (filestream)
159         {
160                 // read all data
161                 result_stream << filestream.rdbuf();
162         }
163
164         std::string result = result_stream.str();
165         std::wstring widened_result;
166
167         // The first 255 codepoints in unicode is the same as in latin1
168         boost::copy(
169                 result | boost::adaptors::transformed(
170                                 [](char c) { return static_cast<unsigned char>(c); }),
171                 std::back_inserter(widened_result));
172
173         return widened_result;
174 }
175
176 std::wstring read_file(const boost::filesystem::path& file)
177 {
178         static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
179
180         if (!boost::filesystem::exists(file))
181         {
182                 return L"";
183         }
184
185         if (boost::filesystem::file_size(file) >= 3)
186         {
187                 boost::filesystem::ifstream bom_stream(file);
188
189                 char header[3];
190                 bom_stream.read(header, 3);
191                 bom_stream.close();
192
193                 if (std::memcmp(BOM, header, 3) == 0)
194                         return read_utf8_file(file);
195         }
196
197         return read_latin1_file(file);
198 }
199
200 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
201 {
202         if (!boost::filesystem::is_regular_file(path))
203                 return L"";
204
205         auto media_info = media_info_repo->get(path.wstring());
206
207         if (!media_info)
208                 return L"";
209
210         auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
211
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());
215
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());
219
220         auto relativePath = get_relative_without_extension(path, env::media_folder());
221         auto str = relativePath.generic_wstring();
222
223         if (str[0] == '\\' || str[0] == '/')
224                 str = std::wstring(str.begin() + 1, str.end());
225
226         return std::wstring()
227                 + L"\"" + str +
228                 + L"\" " + media_info->clip_type +
229                 + L" " + sizeStr +
230                 + L" " + writeTimeWStr +
231                 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
232                 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
233                 + L"\r\n";
234 }
235
236 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
237 {
238         std::wstringstream replyString;
239         for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
240                 replyString << MediaInfo(itr->path(), media_info_repo);
241
242         return boost::to_upper_copy(replyString.str());
243 }
244
245 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
246 {
247         std::wstringstream replyString;
248
249         for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
250         {
251                 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
252                 {
253                         auto relativePath = get_relative_without_extension(itr->path(), env::template_folder());
254
255                         auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
256                         writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
257                         auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
258
259                         auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
260                         sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
261
262                         auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
263
264                         auto dir = relativePath.parent_path();
265                         auto file = boost::to_upper_copy(relativePath.filename().wstring());
266                         relativePath = dir / file;
267
268                         auto str = relativePath.generic_wstring();
269                         boost::trim_if(str, boost::is_any_of("\\/"));
270
271                         auto template_type = cg_registry->get_cg_producer_name(str);
272
273                         replyString << L"\"" << str
274                                                 << L"\" " << sizeWStr
275                                                 << L" " << writeTimeWStr
276                                                 << L" " << template_type
277                                                 << L"\r\n";
278                 }
279         }
280         return replyString.str();
281 }
282
283 std::vector<spl::shared_ptr<core::video_channel>> get_channels(const command_context& ctx)
284 {
285         return cpplinq::from(ctx.channels)
286                 .select([](channel_context c) { return spl::make_shared_ptr(c.channel); })
287                 .to_vector();
288 }
289
290 core::frame_producer_dependencies get_producer_dependencies(const std::shared_ptr<core::video_channel>& channel, const command_context& ctx)
291 {
292         return core::frame_producer_dependencies(
293                         channel->frame_factory(),
294                         get_channels(ctx),
295                         channel->video_format_desc(),
296                         ctx.producer_registry);
297 }
298
299 // Basic Commands
300
301 void loadbg_describer(core::help_sink& sink, const core::help_repository& repository)
302 {
303         sink.short_description(L"Load a media file or resource in the background.");
304         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]})");
305         sink.para()
306                 ->text(L"Loads a producer in the background and prepares it for playout. ")
307                 ->text(L"If no layer is specified the default layer index will be used.");
308         sink.para()
309                 ->code(L"clip")->text(L" will be parsed by available registered producer factories. ")
310                 ->text(L"If a successfully match is found, the producer will be loaded into the background.");
311         sink.para()
312                 ->text(L"If a file with the same name (extension excluded) but with the additional postfix ")
313                 ->code(L"_a")->text(L" is found this file will be used as key for the main ")->code(L"clip")->text(L".");
314         sink.para()
315                 ->code(L"loop")->text(L" will cause the clip to loop.");
316         sink.para()
317                 ->text(L"When playing and looping the clip will start at ")->code(L"frame")->text(L".");
318         sink.para()
319                 ->text(L"When playing and loop the clip will end after ")->code(L"frames")->text(L" number of frames.");
320         sink.para()
321                 ->code(L"auto")->text(L" will cause the clip to automatically start when foreground clip has ended (without play). ")
322                 ->text(LR"(The clip is considered "started" after the optional transition has ended.)");
323         sink.para()->text(L"Examples:");
324         sink.example(L">> LOADBG 1-1 MY_FILE PUSH 20 easeinesine LOOP SEEK 200 LENGTH 400 AUTO FILTER hflip");
325         sink.example(L">> LOADBG 1 MY_FILE PUSH 20 EASEINSINE");
326         sink.example(L">> LOADBG 1-1 MY_FILE SLIDE 10 LEFT");
327         sink.example(L">> LOADBG 1-0 MY_FILE");
328         sink.example(
329                         L">> PLAY 1-1 MY_FILE\n"
330                         L">> LOADBG 1-1 EMPTY MIX 20 AUTO",
331                         L"To automatically fade out a layer after a video file has been played to the end");
332         sink.para()
333                 ->text(L"See ")->see(L"Animation Types")->text(L" for supported values for ")->code(L"tween")->text(L".");
334         sink.para()
335                 ->text(L"See ")->url(L"http://libav.org/libavfilter.html")->text(L" for supported values for the ")
336                 ->code(L"filter")->text(L" command.");
337 }
338
339 std::wstring loadbg_command(command_context& ctx)
340 {
341         transition_info transitionInfo;
342
343         // TRANSITION
344
345         std::wstring message;
346         for (size_t n = 0; n < ctx.parameters.size(); ++n)
347                 message += boost::to_upper_copy(ctx.parameters[n]) + L" ";
348
349         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)?.*)");
350         boost::wsmatch what;
351         if (boost::regex_match(message, what, expr))
352         {
353                 auto transition = what["TRANSITION"].str();
354                 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
355                 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
356                 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
357                 transitionInfo.tweener = tween;
358
359                 if (transition == L"CUT")
360                         transitionInfo.type = transition_type::cut;
361                 else if (transition == L"MIX")
362                         transitionInfo.type = transition_type::mix;
363                 else if (transition == L"PUSH")
364                         transitionInfo.type = transition_type::push;
365                 else if (transition == L"SLIDE")
366                         transitionInfo.type = transition_type::slide;
367                 else if (transition == L"WIPE")
368                         transitionInfo.type = transition_type::wipe;
369
370                 if (direction == L"FROMLEFT")
371                         transitionInfo.direction = transition_direction::from_left;
372                 else if (direction == L"FROMRIGHT")
373                         transitionInfo.direction = transition_direction::from_right;
374                 else if (direction == L"LEFT")
375                         transitionInfo.direction = transition_direction::from_right;
376                 else if (direction == L"RIGHT")
377                         transitionInfo.direction = transition_direction::from_left;
378         }
379
380         //Perform loading of the clip
381         core::diagnostics::scoped_call_context save;
382         core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
383         core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
384
385         auto channel = ctx.channel.channel;
386         auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(channel, ctx), ctx.parameters);
387
388         if (pFP == frame_producer::empty())
389                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(ctx.parameters.size() > 0 ? ctx.parameters[0] : L""));
390
391         bool auto_play = contains_param(L"AUTO", ctx.parameters);
392
393         auto pFP2 = create_transition_producer(channel->video_format_desc().field_mode, pFP, transitionInfo);
394         if (auto_play)
395                 channel->stage().load(ctx.layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
396         else
397                 channel->stage().load(ctx.layer_index(), pFP2, false); // TODO: LOOP
398
399         return L"202 LOADBG OK\r\n";
400 }
401
402 void load_describer(core::help_sink& sink, const core::help_repository& repo)
403 {
404         sink.short_description(L"Load a media file or resource to the foreground.");
405         sink.syntax(LR"(LOAD [video_channel:int]{-[layer:int]|-0} [clip:string] {"additional parameters"})");
406         sink.para()
407                 ->text(L"Loads a clip to the foreground and plays the first frame before pausing. ")
408                 ->text(L"If any clip is playing on the target foreground then this clip will be replaced.");
409         sink.para()->text(L"Examples:");
410         sink.example(L">> LOAD 1 MY_FILE");
411         sink.example(L">> LOAD 1-1 MY_FILE");
412         sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
413 }
414
415 std::wstring load_command(command_context& ctx)
416 {
417         core::diagnostics::scoped_call_context save;
418         core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
419         core::diagnostics::call_context::for_thread().layer = ctx.layer_index();
420         auto pFP = ctx.producer_registry->create_producer(get_producer_dependencies(ctx.channel.channel, ctx), ctx.parameters);
421         ctx.channel.channel->stage().load(ctx.layer_index(), pFP, true);
422
423         return L"202 LOAD OK\r\n";
424 }
425
426 void play_describer(core::help_sink& sink, const core::help_repository& repository)
427 {
428         sink.short_description(L"Play a media file or resource.");
429         sink.syntax(LR"(PLAY [video_channel:int]{-[layer:int]|-0} {[clip:string]} {"additional parameters"})");
430         sink.para()
431                 ->text(L"Moves clip from background to foreground and starts playing it. If a transition (see ")->see(L"LOADBG")
432                 ->text(L") is prepared, it will be executed.");
433         sink.para()
434                 ->text(L"If additional parameters (see ")->see(L"LOADBG")
435                 ->text(L") are provided then the provided clip will first be loaded to the background.");
436         sink.para()->text(L"Examples:");
437         sink.example(L">> PLAY 1 MY_FILE PUSH 20 EASEINSINE");
438         sink.example(L">> PLAY 1-1 MY_FILE SLIDE 10 LEFT");
439         sink.example(L">> PLAY 1-0 MY_FILE");
440         sink.para()->text(L"Note: See ")->see(L"LOADBG")->text(L" for additional details.");
441 }
442
443 std::wstring play_command(command_context& ctx)
444 {
445         if (!ctx.parameters.empty())
446                 loadbg_command(ctx);
447
448         ctx.channel.channel->stage().play(ctx.layer_index());
449
450         return L"202 PLAY OK\r\n";
451 }
452
453 void pause_describer(core::help_sink& sink, const core::help_repository& repo)
454 {
455         sink.short_description(L"Pause playback of a layer.");
456         sink.syntax(L"PAUSE [video_channel:int]{-[layer:int]|-0}");
457         sink.para()
458                 ->text(L"Pauses playback of the foreground clip on the specified layer. The ")->see(L"RESUME")
459                 ->text(L" command can be used to resume playback again.");
460         sink.para()->text(L"Examples:");
461         sink.example(L">> PAUSE 1");
462         sink.example(L">> PAUSE 1-1");
463 }
464
465 std::wstring pause_command(command_context& ctx)
466 {
467         ctx.channel.channel->stage().pause(ctx.layer_index());
468         return L"202 PAUSE OK\r\n";
469 }
470
471 void resume_describer(core::help_sink& sink, const core::help_repository& repo)
472 {
473         sink.short_description(L"Resume playback of a layer.");
474         sink.syntax(L"RESUME [video_channel:int]{-[layer:int]|-0}");
475         sink.para()
476                 ->text(L"Resumes playback of a foreground clip previously paused with the ")
477                 ->see(L"PAUSE")->text(L" command.");
478         sink.para()->text(L"Examples:");
479         sink.example(L">> RESUME 1");
480         sink.example(L">> RESUME 1-1");
481 }
482
483 std::wstring resume_command(command_context& ctx)
484 {
485         ctx.channel.channel->stage().resume(ctx.layer_index());
486         return L"202 RESUME OK\r\n";
487 }
488
489 void stop_describer(core::help_sink& sink, const core::help_repository& repo)
490 {
491         sink.short_description(L"Remove the foreground clip of a layer.");
492         sink.syntax(L"STOP [video_channel:int]{-[layer:int]|-0}");
493         sink.para()
494                 ->text(L"Removes the foreground clip of the specified layer.");
495         sink.para()->text(L"Examples:");
496         sink.example(L">> STOP 1");
497         sink.example(L">> STOP 1-1");
498 }
499
500 std::wstring stop_command(command_context& ctx)
501 {
502         ctx.channel.channel->stage().stop(ctx.layer_index());
503         return L"202 STOP OK\r\n";
504 }
505
506 void clear_describer(core::help_sink& sink, const core::help_repository& repo)
507 {
508         sink.short_description(L"Remove all clips of a layer or an entire channel.");
509         sink.syntax(L"CLEAR [video_channel:int]{-[layer:int]}");
510         sink.para()
511                 ->text(L"Removes all clips (both foreground and background) of the specified layer. ")
512                 ->text(L"If no layer is specified then all layers in the specified ")
513                 ->code(L"video_channel")->text(L" are cleared.");
514         sink.para()->text(L"Examples:");
515         sink.example(L">> CLEAR 1", L"clears everything from the entire channel 1.");
516         sink.example(L">> CLEAR 1-3", L"clears only layer 3 of channel 1.");
517 }
518
519 std::wstring clear_command(command_context& ctx)
520 {
521         int index = ctx.layer_index(std::numeric_limits<int>::min());
522         if (index != std::numeric_limits<int>::min())
523                 ctx.channel.channel->stage().clear(index);
524         else
525                 ctx.channel.channel->stage().clear();
526
527         return L"202 CLEAR OK\r\n";
528 }
529
530 void call_describer(core::help_sink& sink, const core::help_repository& repo)
531 {
532         sink.short_description(L"Call a method on a producer.");
533         sink.syntax(L"CALL [video_channel:int]{-[layer:int]|-0} [param:string]");
534         sink.para()
535                 ->text(L"Calls method on the specified producer with the provided ")
536                 ->code(L"param")->text(L" string.");
537         sink.para()->text(L"Examples:");
538         sink.example(L">> CALL 1 LOOP");
539         sink.example(L">> CALL 1-2 SEEK 25");
540 }
541
542 std::wstring call_command(command_context& ctx)
543 {
544         auto result = ctx.channel.channel->stage().call(ctx.layer_index(), ctx.parameters).get();
545
546         // TODO: because of std::async deferred timed waiting does not work
547
548         /*auto wait_res = result.wait_for(std::chrono::seconds(2));
549         if (wait_res == std::future_status::timeout)
550         CASPAR_THROW_EXCEPTION(timed_out());*/
551
552         std::wstringstream replyString;
553         if (result.empty())
554                 replyString << L"202 CALL OK\r\n";
555         else
556                 replyString << L"201 CALL OK\r\n" << result << L"\r\n";
557
558         return replyString.str();
559 }
560
561 void swap_describer(core::help_sink& sink, const core::help_repository& repo)
562 {
563         sink.short_description(L"Swap layers between channels.");
564         sink.syntax(L"SWAP [channel1:int]{-[layer1:int]} [channel2:int]{-[layer2:int]} {[transforms:TRANSFORMS]}");
565         sink.para()
566                 ->text(L"Swaps layers between channels (both foreground and background will be swapped). ")
567                 ->text(L"By specifying ")->code(L"TRANSFORMS")->text(L" the transformations of the layers are swapped as well.");
568         sink.para()->text(L"If layers are not specified then all layers in respective video channel will be swapped.");
569         sink.para()->text(L"Examples:");
570         sink.example(L">> SWAP 1 2");
571         sink.example(L">> SWAP 1-1 2-3");
572         sink.example(L">> SWAP 1-1 1-2");
573         sink.example(L">> SWAP 1-1 1-2 TRANSFORMS", L"for swapping mixer transformations as well");
574 }
575
576 std::wstring swap_command(command_context& ctx)
577 {
578         bool swap_transforms = ctx.parameters.size() > 1 && boost::iequals(ctx.parameters.at(1), L"TRANSFORMS");
579
580         if (ctx.layer_index(-1) != -1)
581         {
582                 std::vector<std::string> strs;
583                 boost::split(strs, ctx.parameters[0], boost::is_any_of("-"));
584
585                 auto ch1 = ctx.channel.channel;
586                 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(strs.at(0)) - 1);
587
588                 int l1 = ctx.layer_index();
589                 int l2 = boost::lexical_cast<int>(strs.at(1));
590
591                 ch1->stage().swap_layer(l1, l2, ch2.channel->stage(), swap_transforms);
592         }
593         else
594         {
595                 auto ch1 = ctx.channel.channel;
596                 auto ch2 = ctx.channels.at(boost::lexical_cast<int>(ctx.parameters[0]) - 1);
597                 ch1->stage().swap_layers(ch2.channel->stage(), swap_transforms);
598         }
599
600         return L"202 SWAP OK\r\n";
601 }
602
603 void add_describer(core::help_sink& sink, const core::help_repository& repo)
604 {
605         sink.short_description(L"Add a consumer to a video channel.");
606         sink.syntax(L"ADD [video_channel:int]{-[consumer_index:int]} [consumer:string] [parameters:string]");
607         sink.para()
608                 ->text(L"Adds a consumer to the specified video channel. The string ")
609                 ->code(L"consumer")->text(L" will be parsed by the available consumer factories. ")
610                 ->text(L"If a successful match is found a consumer will be created and added to the ")
611                 ->code(L"video_channel")->text(L". Different consumers require different parameters, ")
612                 ->text(L"some examples are below. Consumers can alternatively be specified by adding them to ")
613                 ->see(L"the CasparCG config file")->text(L".");
614         sink.para()
615                 ->text(L"Specifying ")->code(L"consumer_index")
616                 ->text(L" overrides the index that the consumer itself decides and can later be used with the ")
617                 ->see(L"REMOVE")->text(L" command to remove the consumer.");
618         sink.para()->text(L"Examples:");
619         sink.example(L">> ADD 1 DECKLINK 1");
620         sink.example(L">> ADD 1 BLUEFISH 2");
621         sink.example(L">> ADD 1 SCREEN");
622         sink.example(L">> ADD 1 AUDIO");
623         sink.example(L">> ADD 1 IMAGE filename");
624         sink.example(L">> ADD 2 SYNCTO 1");
625         sink.example(L">> ADD 1 FILE filename.mov");
626         sink.example(L">> ADD 1 FILE filename.mov SEPARATE_KEY");
627         sink.example(
628                 L">> ADD 1-700 FILE filename.mov SEPARATE_KEY\n"
629                 L">> REMOVE 1-700", L"overriding the consumer index to easier remove later.");
630         sink.para()->text(L"The streaming consumer is an implementation of the ffmpeg_consumer and supports many of the same arguments:");
631         sink.example(L">> ADD 1 STREAM udp://localhost:5004 -vcodec libx264 -tune zerolatency -preset ultrafast -crf 25 -format mpegts -vf scale=240:180");
632 }
633
634 std::wstring add_command(command_context& ctx)
635 {
636         replace_placeholders(
637                         L"<CLIENT_IP_ADDRESS>",
638                         ctx.client->address(),
639                         ctx.parameters);
640
641         core::diagnostics::scoped_call_context save;
642         core::diagnostics::call_context::for_thread().video_channel = ctx.channel_index + 1;
643
644         auto consumer = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx));
645         ctx.channel.channel->output().add(ctx.layer_index(consumer->index()), consumer);
646
647         return L"202 ADD OK\r\n";
648 }
649
650 void remove_describer(core::help_sink& sink, const core::help_repository& repo)
651 {
652         sink.short_description(L"Remove a consumer from a video channel.");
653         sink.syntax(L"REMOVE [video_channel:int]{-[consumer_index:int]} {[parameters:string]}");
654         sink.para()
655                 ->text(L"Removes an existing consumer from ")->code(L"video_channel")
656                 ->text(L". If ")->code(L"consumer_index")->text(L" is given, the consumer will be removed via its id. If ")
657                 ->code(L"parameters")->text(L" are given instead, the consumer matching those parameters will be removed.");
658         sink.para()->text(L"Examples:");
659         sink.example(L">> REMOVE 1 DECKLINK 1");
660         sink.example(L">> REMOVE 1 BLUEFISH 2");
661         sink.example(L">> REMOVE 1 SCREEN");
662         sink.example(L">> REMOVE 1 AUDIO");
663         sink.example(L">> REMOVE 1-300", L"for removing the consumer with index 300 from channel 1");
664 }
665
666 std::wstring remove_command(command_context& ctx)
667 {
668         auto index = ctx.layer_index(std::numeric_limits<int>::min());
669
670         if (index == std::numeric_limits<int>::min())
671         {
672                 replace_placeholders(
673                                 L"<CLIENT_IP_ADDRESS>",
674                                 ctx.client->address(),
675                                 ctx.parameters);
676
677                 index = ctx.consumer_registry->create_consumer(ctx.parameters, &ctx.channel.channel->stage(), get_channels(ctx))->index();
678         }
679
680         ctx.channel.channel->output().remove(index);
681
682         return L"202 REMOVE OK\r\n";
683 }
684
685 void print_describer(core::help_sink& sink, const core::help_repository& repo)
686 {
687         sink.short_description(L"Take a snapshot of a channel.");
688         sink.syntax(L"PRINT [video_channel:int]");
689         sink.para()
690                 ->text(L"Saves an RGBA PNG bitmap still image of the contents of the specified channel in the ")
691                 ->code(L"media")->text(L" folder.");
692         sink.para()->text(L"Examples:");
693         sink.example(L">> PRINT 1", L"will produce a PNG image with the current date and time as the filename for example 20130620T192220.png");
694 }
695
696 std::wstring print_command(command_context& ctx)
697 {
698         ctx.channel.channel->output().add(ctx.consumer_registry->create_consumer({ L"IMAGE" }, &ctx.channel.channel->stage(), get_channels(ctx)));
699
700         return L"202 PRINT OK\r\n";
701 }
702
703 void log_level_describer(core::help_sink& sink, const core::help_repository& repo)
704 {
705         sink.short_description(L"Change the log level of the server.");
706         sink.syntax(L"LOG LEVEL [level:trace,debug,info,warning,error,fatal]");
707         sink.para()->text(L"Changes the log level of the server.");
708         sink.para()->text(L"Examples:");
709         sink.example(L">> LOG LEVEL trace");
710         sink.example(L">> LOG LEVEL info");
711 }
712
713 std::wstring log_level_command(command_context& ctx)
714 {
715         log::set_log_level(ctx.parameters.at(0));
716
717         return L"202 LOG OK\r\n";
718 }
719
720 void log_category_describer(core::help_sink& sink, const core::help_repository& repo)
721 {
722         sink.short_description(L"Enable/disable a logging category in the server.");
723         sink.syntax(L"LOG CATEGORY [category:calltrace,communication] [enable:0,1]");
724         sink.para()->text(L"Enables or disables the specified logging category.");
725         sink.para()->text(L"Examples:");
726         sink.example(L">> LOG CATEGORY calltrace 1", L"to enable call trace");
727         sink.example(L">> LOG CATEGORY calltrace 0", L"to disable call trace");
728 }
729
730 std::wstring log_category_command(command_context& ctx)
731 {
732         log::set_log_category(ctx.parameters.at(0), ctx.parameters.at(1) == L"1");
733
734         return L"202 LOG OK\r\n";
735 }
736
737 void set_describer(core::help_sink& sink, const core::help_repository& repo)
738 {
739         sink.short_description(L"Change the value of a channel variable.");
740         sink.syntax(L"SET [video_channel:int] [variable:string] [value:string]");
741         sink.para()->text(L"Changes the value of a channel variable. Available variables to set:");
742         sink.definitions()
743                 ->item(L"MODE", L"Changes the video format of the channel.")
744                 ->item(L"CHANNEL_LAYOUT", L"Changes the audio channel layout of the video channel channel.");
745         sink.para()->text(L"Examples:");
746         sink.example(L">> SET 1 MODE PAL", L"changes the video mode on channel 1 to PAL.");
747         sink.example(L">> SET 1 CHANNEL_LAYOUT smpte", L"changes the audio channel layout on channel 1 to smpte.");
748 }
749
750 std::wstring set_command(command_context& ctx)
751 {
752         std::wstring name = boost::to_upper_copy(ctx.parameters[0]);
753         std::wstring value = boost::to_upper_copy(ctx.parameters[1]);
754
755         if (name == L"MODE")
756         {
757                 auto format_desc = core::video_format_desc(value);
758                 if (format_desc.format != core::video_format::invalid)
759                 {
760                         ctx.channel.channel->video_format_desc(format_desc);
761                         return L"202 SET MODE OK\r\n";
762                 }
763
764                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid video mode"));
765         }
766         else if (name == L"CHANNEL_LAYOUT")
767         {
768                 auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(value);
769
770                 if (channel_layout)
771                 {
772                         ctx.channel.channel->audio_channel_layout(*channel_layout);
773                         return L"202 SET CHANNEL_LAYOUT OK\r\n";
774                 }
775
776                 CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid audio channel layout"));
777         }
778
779         CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Invalid channel variable"));
780 }
781
782 void data_store_describer(core::help_sink& sink, const core::help_repository& repo)
783 {
784         sink.short_description(L"Store a dataset.");
785         sink.syntax(L"DATA STORE [name:string] [data:string]");
786         sink.para()->text(L"Stores the dataset data under the name ")->code(L"name")->text(L".");
787         sink.para()->text(L"Directories will be created if they do not exist.");
788         sink.para()->text(L"Examples:");
789         sink.example(LR"(>> DATA STORE my_data "Some useful data")");
790         sink.example(LR"(>> DATA STORE Folder1/my_data "Some useful data")");
791 }
792
793 std::wstring data_store_command(command_context& ctx)
794 {
795         std::wstring filename = env::data_folder();
796         filename.append(ctx.parameters[0]);
797         filename.append(L".ftd");
798
799         auto data_path = boost::filesystem::path(filename).parent_path().wstring();
800         auto found_data_path = find_case_insensitive(data_path);
801
802         if (found_data_path)
803                 data_path = *found_data_path;
804
805         if (!boost::filesystem::exists(data_path))
806                 boost::filesystem::create_directories(data_path);
807
808         auto found_filename = find_case_insensitive(filename);
809
810         if (found_filename)
811                 filename = *found_filename; // Overwrite case insensitive.
812
813         boost::filesystem::wofstream datafile(filename);
814         if (!datafile)
815                 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Could not open file " + filename));
816
817         datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
818         datafile << ctx.parameters[1] << std::flush;
819         datafile.close();
820
821         return L"202 DATA STORE OK\r\n";
822 }
823
824 void data_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
825 {
826         sink.short_description(L"Retrieve a dataset.");
827         sink.syntax(L"DATA RETRIEVE [name:string]");
828         sink.para()->text(L"Returns the data saved under the name ")->code(L"name")->text(L".");
829         sink.para()->text(L"Examples:");
830         sink.example(L">> DATA RETRIEVE my_data");
831         sink.example(L">> DATA RETRIEVE Folder1/my_data");
832 }
833
834 std::wstring data_retrieve_command(command_context& ctx)
835 {
836         std::wstring filename = env::data_folder();
837         filename.append(ctx.parameters[0]);
838         filename.append(L".ftd");
839
840         std::wstring file_contents;
841
842         auto found_file = find_case_insensitive(filename);
843
844         if (found_file)
845                 file_contents = read_file(boost::filesystem::path(*found_file));
846
847         if (file_contents.empty())
848                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
849
850         std::wstringstream reply;
851         reply << L"201 DATA RETRIEVE OK\r\n";
852
853         std::wstringstream file_contents_stream(file_contents);
854         std::wstring line;
855
856         bool firstLine = true;
857         while (std::getline(file_contents_stream, line))
858         {
859                 if (firstLine)
860                         firstLine = false;
861                 else
862                         reply << "\n";
863
864                 reply << line;
865         }
866
867         reply << "\r\n";
868         return reply.str();
869 }
870
871 void data_list_describer(core::help_sink& sink, const core::help_repository& repo)
872 {
873         sink.short_description(L"List stored datasets.");
874         sink.syntax(L"DATA LIST");
875         sink.para()->text(L"Returns a list of all stored datasets.");
876 }
877
878 std::wstring data_list_command(command_context& ctx)
879 {
880         std::wstringstream replyString;
881         replyString << L"200 DATA LIST OK\r\n";
882
883         for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
884         {
885                 if (boost::filesystem::is_regular_file(itr->path()))
886                 {
887                         if (!boost::iequals(itr->path().extension().wstring(), L".ftd"))
888                                 continue;
889
890                         auto relativePath = get_relative_without_extension(itr->path(), env::data_folder());
891                         auto str = relativePath.generic_wstring();
892
893                         if (str[0] == L'\\' || str[0] == L'/')
894                                 str = std::wstring(str.begin() + 1, str.end());
895
896                         replyString << str << L"\r\n";
897                 }
898         }
899
900         replyString << L"\r\n";
901
902         return boost::to_upper_copy(replyString.str());
903 }
904
905 void data_remove_describer(core::help_sink& sink, const core::help_repository& repo)
906 {
907         sink.short_description(L"Remove a stored dataset.");
908         sink.syntax(L"DATA REMOVE [name:string]");
909         sink.para()->text(L"Removes the dataset saved under the name ")->code(L"name")->text(L".");
910         sink.para()->text(L"Examples:");
911         sink.example(L">> DATA REMOVE my_data");
912         sink.example(L">> DATA REMOVE Folder1/my_data");
913 }
914
915 std::wstring data_remove_command(command_context& ctx)
916 {
917         std::wstring filename = env::data_folder();
918         filename.append(ctx.parameters[0]);
919         filename.append(L".ftd");
920
921         if (!boost::filesystem::exists(filename))
922                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
923
924         if (!boost::filesystem::remove(filename))
925                 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(filename + L" could not be removed"));
926
927         return L"202 DATA REMOVE OK\r\n";
928 }
929
930 // Template Graphics Commands
931
932 void cg_add_describer(core::help_sink& sink, const core::help_repository& repo)
933 {
934         sink.short_description(L"Prepare a template for displaying.");
935         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} ADD [cg_layer:int] [template:string] [play-on-load:0,1] {[data]}");
936         sink.para()
937                 ->text(L"Prepares a template for displaying. It won't show until you call ")->see(L"CG PLAY")
938                 ->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.");
939         sink.para()->text(L"Examples:");
940         sink.example(L"CG 1 ADD 10 svtnews/info 1");
941 }
942
943 std::wstring cg_add_command(command_context& ctx)
944 {
945         //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
946
947         int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
948         std::wstring label;             //_parameters[2]
949         bool bDoStart = false;          //_parameters[2] alt. _parameters[3]
950         unsigned int dataIndex = 3;
951
952         if (ctx.parameters.at(2).length() > 1)
953         {       //read label
954                 label = ctx.parameters.at(2);
955                 ++dataIndex;
956
957                 if (ctx.parameters.at(3).length() > 0)  //read play-on-load-flag
958                         bDoStart = (ctx.parameters.at(3).at(0) == L'1') ? true : false;
959         }
960         else
961         {       //read play-on-load-flag
962                 bDoStart = (ctx.parameters.at(2).at(0) == L'1') ? true : false;
963         }
964
965         const wchar_t* pDataString = 0;
966         std::wstring dataFromFile;
967         if (ctx.parameters.size() > dataIndex)
968         {       //read data
969                 const std::wstring& dataString = ctx.parameters.at(dataIndex);
970
971                 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
972                         pDataString = dataString.c_str();
973                 else
974                 {
975                         //The data is not an XML-string, it must be a filename
976                         std::wstring filename = env::data_folder();
977                         filename.append(dataString);
978                         filename.append(L".ftd");
979
980                         auto found_file = find_case_insensitive(filename);
981
982                         if (found_file)
983                         {
984                                 dataFromFile = read_file(boost::filesystem::path(*found_file));
985                                 pDataString = dataFromFile.c_str();
986                         }
987                 }
988         }
989
990         auto filename = ctx.parameters.at(1);
991         auto proxy = ctx.cg_registry->get_or_create_proxy(
992                 spl::make_shared_ptr(ctx.channel.channel),
993                 get_producer_dependencies(ctx.channel.channel, ctx),
994                 ctx.layer_index(core::cg_proxy::DEFAULT_LAYER),
995                 filename);
996
997         if (proxy == core::cg_proxy::empty())
998                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Could not find template " + filename));
999         else
1000                 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
1001
1002         return L"202 CG OK\r\n";
1003 }
1004
1005 void cg_play_describer(core::help_sink& sink, const core::help_repository& repo)
1006 {
1007         sink.short_description(L"Play and display a template.");
1008         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} PLAY [cg_layer:int]");
1009         sink.para()->text(L"Plays and displays the template in the specified layer.");
1010         sink.para()->text(L"Examples:");
1011         sink.example(L"CG 1 PLAY 0");
1012 }
1013
1014 std::wstring cg_play_command(command_context& ctx)
1015 {
1016         int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1017         ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1018
1019         return L"202 CG OK\r\n";
1020 }
1021
1022 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
1023 {
1024         auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1025
1026         if (proxy == cg_proxy::empty())
1027                 CASPAR_THROW_EXCEPTION(expected_user_error() << msg_info(L"No CG proxy running on layer"));
1028
1029         return proxy;
1030 }
1031
1032 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
1033 {
1034         sink.short_description(L"Stop and remove a template.");
1035         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
1036         sink.para()
1037                 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
1038                 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1039         sink.para()->text(L"Examples:");
1040         sink.example(L"CG 1 STOP 0");
1041 }
1042
1043 std::wstring cg_stop_command(command_context& ctx)
1044 {
1045         int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1046         get_expected_cg_proxy(ctx)->stop(layer, 0);
1047
1048         return L"202 CG OK\r\n";
1049 }
1050
1051 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1052 {
1053         sink.short_description(LR"(Trigger a "continue" in a template.)");
1054         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1055         sink.para()
1056                 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1057                 ->text(L"This is used to control animations that has multiple discreet steps.");
1058         sink.para()->text(L"Examples:");
1059         sink.example(L"CG 1 NEXT 0");
1060 }
1061
1062 std::wstring cg_next_command(command_context& ctx)
1063 {
1064         int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1065         get_expected_cg_proxy(ctx)->next(layer);
1066
1067         return L"202 CG OK\r\n";
1068 }
1069
1070 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1071 {
1072         sink.short_description(L"Remove a template.");
1073         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1074         sink.para()->text(L"Removes the template from the specified layer.");
1075         sink.para()->text(L"Examples:");
1076         sink.example(L"CG 1 REMOVE 0");
1077 }
1078
1079 std::wstring cg_remove_command(command_context& ctx)
1080 {
1081         int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1082         get_expected_cg_proxy(ctx)->remove(layer);
1083
1084         return L"202 CG OK\r\n";
1085 }
1086
1087 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1088 {
1089         sink.short_description(L"Remove all templates on a video layer.");
1090         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1091         sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1092         sink.para()->text(L"Examples:");
1093         sink.example(L"CG 1 CLEAR");
1094 }
1095
1096 std::wstring cg_clear_command(command_context& ctx)
1097 {
1098         ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1099
1100         return L"202 CG OK\r\n";
1101 }
1102
1103 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1104 {
1105         sink.short_description(L"Update a template with new data.");
1106         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1107         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.");
1108 }
1109
1110 std::wstring cg_update_command(command_context& ctx)
1111 {
1112         int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1113
1114         std::wstring dataString = ctx.parameters.at(1);
1115         if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1116         {
1117                 //The data is not XML or Json, it must be a filename
1118                 std::wstring filename = env::data_folder();
1119                 filename.append(dataString);
1120                 filename.append(L".ftd");
1121
1122                 dataString = read_file(boost::filesystem::path(filename));
1123         }
1124
1125         get_expected_cg_proxy(ctx)->update(layer, dataString);
1126
1127         return L"202 CG OK\r\n";
1128 }
1129
1130 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1131 {
1132         sink.short_description(L"Invoke a method/label on a template.");
1133         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1134         sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1135         sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1136 }
1137
1138 std::wstring cg_invoke_command(command_context& ctx)
1139 {
1140         std::wstringstream replyString;
1141         replyString << L"201 CG OK\r\n";
1142         int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1143         auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1144         replyString << result << L"\r\n";
1145
1146         return replyString.str();
1147 }
1148
1149 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1150 {
1151         sink.short_description(L"Get information about a running template or the template host.");
1152         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1153         sink.para()->text(L"Retrieves information about the template on the specified layer.");
1154         sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1155 }
1156
1157 std::wstring cg_info_command(command_context& ctx)
1158 {
1159         std::wstringstream replyString;
1160         replyString << L"201 CG OK\r\n";
1161
1162         if (ctx.parameters.empty())
1163         {
1164                 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1165                 replyString << info << L"\r\n";
1166         }
1167         else
1168         {
1169                 int layer = boost::lexical_cast<int>(ctx.parameters.at(0));
1170                 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1171
1172                 replyString << desc << L"\r\n";
1173         }
1174
1175         return replyString.str();
1176 }
1177
1178 // Mixer Commands
1179
1180 core::frame_transform get_current_transform(command_context& ctx)
1181 {
1182         return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1183 }
1184
1185 template<typename Func>
1186 std::wstring reply_value(command_context& ctx, const Func& extractor)
1187 {
1188         auto value = extractor(get_current_transform(ctx));
1189
1190         return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1191 }
1192
1193 class transforms_applier
1194 {
1195         static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1196
1197         std::vector<stage::transform_tuple_t>   transforms_;
1198         command_context&                                                ctx_;
1199         bool                                                                    defer_;
1200 public:
1201         transforms_applier(command_context& ctx)
1202                 : ctx_(ctx)
1203         {
1204                 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1205
1206                 if (defer_)
1207                         ctx.parameters.pop_back();
1208         }
1209
1210         void add(stage::transform_tuple_t&& transform)
1211         {
1212                 transforms_.push_back(std::move(transform));
1213         }
1214
1215         void commit_deferred()
1216         {
1217                 auto& transforms = deferred_transforms_[ctx_.channel_index];
1218                 ctx_.channel.channel->stage().apply_transforms(transforms).get();
1219                 transforms.clear();
1220         }
1221
1222         void apply()
1223         {
1224                 if (defer_)
1225                 {
1226                         auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1227                         defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1228                 }
1229                 else
1230                         ctx_.channel.channel->stage().apply_transforms(transforms_);
1231         }
1232 };
1233 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1234
1235 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1236 {
1237         sink.short_description(L"Let a layer act as alpha for the one obove.");
1238         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1239         sink.para()
1240                 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1241                 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1242                 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1243                 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1244                 ->text(L"instead it will be used as the key for the layer above.");
1245         sink.para()->text(L"Examples:");
1246         sink.example(L">> MIXER 1-0 KEYER 1");
1247         sink.example(
1248                 L">> MIXER 1-0 KEYER\n"
1249                 L"<< 201 MIXER OK\n"
1250                 L"<< 1", L"to retrieve the current state");
1251 }
1252
1253 std::wstring mixer_keyer_command(command_context& ctx)
1254 {
1255         if (ctx.parameters.empty())
1256                 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1257
1258         transforms_applier transforms(ctx);
1259         bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1260         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1261         {
1262                 transform.image_transform.is_key = value;
1263                 return transform;
1264         }, 0, tweener(L"linear")));
1265         transforms.apply();
1266
1267         return L"202 MIXER OK\r\n";
1268 }
1269
1270 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1271
1272 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1273 {
1274         sink.short_description(L"Enable chroma keying on a layer.");
1275         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[enable:0,1] {[target_hue:float] [hue_width:float] [min_saturation:float] [min_brightness:float] [softness:float] [spill:float] [spill_darken:float] [show_mask:0,1]}}" + ANIMATION_SYNTAX);
1276         sink.para()
1277                 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1278         sink.para()->text(L"The chroma keying is done in the HSB/HSV color space.");
1279         sink.para()->text(L"Parameters:");
1280         sink.definitions()
1281                 ->item(L"enable", L"0 to disable chroma keying on layer. The rest of the parameters should not be given when disabling.")
1282                 ->item(L"target_hue", L"The hue in degrees between 0-360 where the center of the hue window will open up.")
1283                 ->item(L"hue_width", L"The width of the hue window within 0.0-1.0 where 1.0 means 100% of 360 degrees around target_hue.")
1284                 ->item(L"min_saturation", L"The minimum saturation within 0.0-1.0 required for a color to be within the chroma window.")
1285                 ->item(L"min_brightness", L"The minimum brightness within 0.0-1.0 required for a color to be within the chroma window.")
1286                 ->item(L"softness", L"The softness of the chroma keying window.")
1287                 ->item(L"spill", L"Controls the amount of spill. A value of 1.0 does not suppress any spill. A lower value gradually turns the spill into grayscale and more transparent.")
1288                 ->item(L"spill_darken", L"Controls the shade of gray that the spill suppression is done towards. Lower values goes towards white and higher values goes towards black.")
1289                 ->item(L"show_mask", L"If enabled, only shows the mask. Useful while editing the chroma key settings.")
1290                 ;
1291         sink.example(L">> MIXER 1-1 CHROMA 1 120 0.1 0 0 0.1 1 2 0", L"for enabling chroma keying centered around a hue of 120 degrees (green) and with a 10% hue width");
1292         sink.example(L">> MIXER 1-1 CHROMA 0", L"for disabling chroma keying");
1293         sink.example(
1294                 L">> MIXER 1-1 CHROMA 0\n"
1295                 L"<< 202 MIXER OK\n"
1296                 L"<< 1 120 0.1 0 0 0.1 1 2 0", L"for getting the current chroma key mode");
1297         sink.para()->text(L"Deprecated legacy syntax:");
1298         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] [spill:float]}}" + ANIMATION_SYNTAX);
1299         sink.para()->text(L"Deprecated legacy examples:");
1300         sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 1.0 25 easeinsine");
1301         sink.example(L">> MIXER 1-1 CHROMA none");
1302 }
1303
1304 std::wstring mixer_chroma_command(command_context& ctx)
1305 {
1306         if (ctx.parameters.empty())
1307         {
1308                 auto chroma = get_current_transform(ctx).image_transform.chroma;
1309                 return L"201 MIXER OK\r\n"
1310                         + std::wstring(chroma.enable ? L"1 " : L"0 ")
1311                         + boost::lexical_cast<std::wstring>(chroma.target_hue) + L" "
1312                         + boost::lexical_cast<std::wstring>(chroma.hue_width) + L" "
1313                         + boost::lexical_cast<std::wstring>(chroma.min_saturation) + L" "
1314                         + boost::lexical_cast<std::wstring>(chroma.min_brightness) + L" "
1315                         + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1316                         + boost::lexical_cast<std::wstring>(chroma.spill) + L" "
1317                         + boost::lexical_cast<std::wstring>(chroma.spill_darken) + L" "
1318                         + std::wstring(chroma.show_mask ? L"1" : L"0") + L"\r\n";
1319         }
1320
1321         transforms_applier transforms(ctx);
1322         core::chroma chroma;
1323
1324         int duration;
1325         std::wstring tween;
1326
1327         auto legacy_mode = core::get_chroma_mode(ctx.parameters.at(0));
1328
1329         if (legacy_mode)
1330         {
1331
1332                 duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1333                 tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1334
1335                 if (*legacy_mode == chroma::legacy_type::none)
1336                 {
1337                         chroma.enable = false;
1338                 }
1339                 else
1340                 {
1341                         chroma.enable = true;
1342                         chroma.hue_width = 0.5 - boost::lexical_cast<double>(ctx.parameters.at(1)) * 0.5;
1343                         chroma.min_brightness = boost::lexical_cast<double>(ctx.parameters.at(1));
1344                         chroma.min_saturation = boost::lexical_cast<double>(ctx.parameters.at(1));
1345                         chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2)) - boost::lexical_cast<double>(ctx.parameters.at(1));
1346                         chroma.spill = boost::lexical_cast<double>(ctx.parameters.at(3));
1347                         chroma.spill_darken = 2;
1348
1349                         if (*legacy_mode == chroma::legacy_type::green)
1350                                 chroma.target_hue = 120;
1351                         else if (*legacy_mode == chroma::legacy_type::blue)
1352                                 chroma.target_hue = 240;
1353                 }
1354         }
1355         else
1356         {
1357                 duration = ctx.parameters.size() > 9 ? boost::lexical_cast<int>(ctx.parameters.at(9)) : 0;
1358                 tween = ctx.parameters.size() > 10 ? ctx.parameters.at(10) : L"linear";
1359
1360                 chroma.enable = ctx.parameters.at(0) == L"1";
1361
1362                 if (chroma.enable)
1363                 {
1364                         chroma.target_hue               = boost::lexical_cast<double>(ctx.parameters.at(1));
1365                         chroma.hue_width                = boost::lexical_cast<double>(ctx.parameters.at(2));
1366                         chroma.min_saturation   = boost::lexical_cast<double>(ctx.parameters.at(3));
1367                         chroma.min_brightness   = boost::lexical_cast<double>(ctx.parameters.at(4));
1368                         chroma.softness                 = boost::lexical_cast<double>(ctx.parameters.at(5));
1369                         chroma.spill                    = boost::lexical_cast<double>(ctx.parameters.at(6));
1370                         chroma.spill_darken             = boost::lexical_cast<double>(ctx.parameters.at(7));
1371                         chroma.show_mask                = boost::lexical_cast<double>(ctx.parameters.at(8));
1372                 }
1373         }
1374
1375
1376         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1377         {
1378                 transform.image_transform.chroma = chroma;
1379                 return transform;
1380         }, duration, tween));
1381         transforms.apply();
1382
1383         return L"202 MIXER OK\r\n";
1384 }
1385
1386 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1387 {
1388         sink.short_description(L"Set the blend mode for a layer.");
1389         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1390         sink.para()
1391                 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1392                 ->text(L"If no argument is given the current blend mode is returned.");
1393         sink.para()
1394                 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1395                 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1396                 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1397                 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1398         sink.para()->text(L"Examples:");
1399         sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1400         sink.example(
1401                 L">> MIXER 1-1 BLEND\n"
1402                 L"<< 201 MIXER OK\n"
1403                 L"<< SCREEN", L"for getting the current blend mode");
1404         sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1405 }
1406
1407 std::wstring mixer_blend_command(command_context& ctx)
1408 {
1409         if (ctx.parameters.empty())
1410                 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1411
1412         transforms_applier transforms(ctx);
1413         auto value = get_blend_mode(ctx.parameters.at(0));
1414         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1415         {
1416                 transform.image_transform.blend_mode = value;
1417                 return transform;
1418         }, 0, tweener(L"linear")));
1419         transforms.apply();
1420
1421         return L"202 MIXER OK\r\n";
1422 }
1423
1424 template<typename Getter, typename Setter>
1425 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1426 {
1427         if (ctx.parameters.empty())
1428                 return reply_value(ctx, getter);
1429
1430         transforms_applier transforms(ctx);
1431         double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1432         int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1433         std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1434
1435         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1436         {
1437                 setter(transform, value);
1438                 return transform;
1439         }, duration, tween));
1440         transforms.apply();
1441
1442         return L"202 MIXER OK\r\n";
1443 }
1444
1445 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1446 {
1447         sink.short_description(L"Change the opacity of a layer.");
1448         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1449         sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1450         sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1451         sink.para()->text(L"Examples:");
1452         sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1453         sink.example(
1454                 L">> MIXER 1-0 OPACITY\n"
1455                 L"<< 201 MIXER OK\n"
1456                 L"<< 0.5", L"to retrieve the current opacity");
1457 }
1458
1459 std::wstring mixer_opacity_command(command_context& ctx)
1460 {
1461         return single_double_animatable_mixer_command(
1462                         ctx,
1463                         [](const frame_transform& t) { return t.image_transform.opacity; },
1464                         [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1465 }
1466
1467 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1468 {
1469         sink.short_description(L"Change the brightness of a layer.");
1470         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1471         sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1472         sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1473         sink.para()->text(L"Examples:");
1474         sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1475         sink.example(
1476                 L">> MIXER 1-0 BRIGHTNESS\n"
1477                 L"<< 201 MIXER OK\n"
1478                 L"0.5", L"to retrieve the current brightness");
1479 }
1480
1481 std::wstring mixer_brightness_command(command_context& ctx)
1482 {
1483         return single_double_animatable_mixer_command(
1484                         ctx,
1485                         [](const frame_transform& t) { return t.image_transform.brightness; },
1486                         [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1487 }
1488
1489 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1490 {
1491         sink.short_description(L"Change the saturation of a layer.");
1492         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1493         sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1494         sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1495         sink.para()->text(L"Examples:");
1496         sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1497         sink.example(
1498                 L">> MIXER 1-0 SATURATION\n"
1499                 L"<< 201 MIXER OK\n"
1500                 L"<< 0.5", L"to retrieve the current saturation");
1501 }
1502
1503 std::wstring mixer_saturation_command(command_context& ctx)
1504 {
1505         return single_double_animatable_mixer_command(
1506                         ctx,
1507                         [](const frame_transform& t) { return t.image_transform.saturation; },
1508                         [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1509 }
1510
1511 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1512 {
1513         sink.short_description(L"Change the contrast of a layer.");
1514         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1515         sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1516         sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1517         sink.para()->text(L"Examples:");
1518         sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1519         sink.example(
1520                 L">> MIXER 1-0 CONTRAST\n"
1521                 L"<< 201 MIXER OK\n"
1522                 L"<< 0.5", L"to retrieve the current contrast");
1523 }
1524
1525 std::wstring mixer_contrast_command(command_context& ctx)
1526 {
1527         return single_double_animatable_mixer_command(
1528                         ctx,
1529                         [](const frame_transform& t) { return t.image_transform.contrast; },
1530                         [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1531 }
1532
1533 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1534 {
1535         sink.short_description(L"Adjust the video levels of a layer.");
1536         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);
1537         sink.para()
1538                 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1539         sink.definitions()
1540                 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1541                 ->item(L"gamma", L"Adjusts the gamma of the image.")
1542                 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1543                 sink.para()->text(L"Examples:");
1544         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");
1545         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");
1546         sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1547         sink.example(
1548                 L">> MIXER 1-10 LEVELS\n"
1549                 L"<< 201 MIXER OK\n"
1550                 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1551 }
1552
1553 std::wstring mixer_levels_command(command_context& ctx)
1554 {
1555         if (ctx.parameters.empty())
1556         {
1557                 auto levels = get_current_transform(ctx).image_transform.levels;
1558                 return L"201 MIXER OK\r\n"
1559                         + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1560                         + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1561                         + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1562                         + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1563                         + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1564         }
1565
1566         transforms_applier transforms(ctx);
1567         levels value;
1568         value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1569         value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1570         value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1571         value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1572         value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1573         int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1574         std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1575
1576         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1577         {
1578                 transform.image_transform.levels = value;
1579                 return transform;
1580         }, duration, tween));
1581         transforms.apply();
1582
1583         return L"202 MIXER OK\r\n";
1584 }
1585
1586 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1587 {
1588         sink.short_description(L"Change the fill position and scale of a layer.");
1589         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1590         sink.para()
1591                 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1592                 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1593                 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1594                 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1595         sink.para()
1596                 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1597                 ->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.");
1598         sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1599         sink.para()
1600                 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1601                 ->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, ")
1602                 ->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");
1603         sink.definitions()
1604                 ->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.")
1605                 ->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.")
1606                 ->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.")
1607                 ->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.");
1608         sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1609         sink.para()->text(L"Examples:");
1610         sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1611         sink.example(
1612                 L">> MIXER 1-0 FILL\n"
1613                 L"<< 201 MIXER OK\n"
1614                 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1615 }
1616
1617 std::wstring mixer_fill_command(command_context& ctx)
1618 {
1619         if (ctx.parameters.empty())
1620         {
1621                 auto transform = get_current_transform(ctx).image_transform;
1622                 auto translation = transform.fill_translation;
1623                 auto scale = transform.fill_scale;
1624                 return L"201 MIXER OK\r\n"
1625                         + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1626                         + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1627                         + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1628                         + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1629         }
1630
1631         transforms_applier transforms(ctx);
1632         int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1633         std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1634         double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1635         double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1636         double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1637         double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1638
1639         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1640         {
1641                 transform.image_transform.fill_translation[0] = x;
1642                 transform.image_transform.fill_translation[1] = y;
1643                 transform.image_transform.fill_scale[0] = x_s;
1644                 transform.image_transform.fill_scale[1] = y_s;
1645                 return transform;
1646         }, duration, tween));
1647         transforms.apply();
1648
1649         return L"202 MIXER OK\r\n";
1650 }
1651
1652 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1653 {
1654         sink.short_description(L"Change the clipping viewport of a layer.");
1655         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1656         sink.para()
1657                 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1658                 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1659                 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1660         sink.definitions()
1661                 ->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.")
1662                 ->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.")
1663                 ->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.")
1664                 ->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.");
1665         sink.para()->text(L"Examples:");
1666         sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1667         sink.example(
1668                 L">> MIXER 1-0 CLIP\n"
1669                 L"<< 201 MIXER OK\n"
1670                 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1671 }
1672
1673 std::wstring mixer_clip_command(command_context& ctx)
1674 {
1675         if (ctx.parameters.empty())
1676         {
1677                 auto transform = get_current_transform(ctx).image_transform;
1678                 auto translation = transform.clip_translation;
1679                 auto scale = transform.clip_scale;
1680
1681                 return L"201 MIXER OK\r\n"
1682                         + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1683                         + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1684                         + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1685                         + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1686         }
1687
1688         transforms_applier transforms(ctx);
1689         int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1690         std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1691         double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1692         double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1693         double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1694         double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1695
1696         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1697         {
1698                 transform.image_transform.clip_translation[0] = x;
1699                 transform.image_transform.clip_translation[1] = y;
1700                 transform.image_transform.clip_scale[0] = x_s;
1701                 transform.image_transform.clip_scale[1] = y_s;
1702                 return transform;
1703         }, duration, tween));
1704         transforms.apply();
1705
1706         return L"202 MIXER OK\r\n";
1707 }
1708
1709 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1710 {
1711         sink.short_description(L"Change the anchor point of a layer.");
1712         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1713         sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1714         sink.para()
1715                 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1716                 ->text(L" will be done from.");
1717         sink.definitions()
1718                 ->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.")
1719                 ->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.");
1720         sink.para()->text(L"Examples:");
1721         sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1722         sink.example(
1723                 L">> MIXER 1-10 ANCHOR\n"
1724                 L"<< 201 MIXER OK\n"
1725                 L"<< 0.5 0.6", L"gets the anchor point");
1726 }
1727
1728 std::wstring mixer_anchor_command(command_context& ctx)
1729 {
1730         if (ctx.parameters.empty())
1731         {
1732                 auto transform = get_current_transform(ctx).image_transform;
1733                 auto anchor = transform.anchor;
1734                 return L"201 MIXER OK\r\n"
1735                         + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1736                         + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1737         }
1738
1739         transforms_applier transforms(ctx);
1740         int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1741         std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1742         double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1743         double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1744
1745         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1746         {
1747                 transform.image_transform.anchor[0] = x;
1748                 transform.image_transform.anchor[1] = y;
1749                 return transform;
1750         }, duration, tween));
1751         transforms.apply();
1752
1753         return L"202 MIXER OK\r\n";
1754 }
1755
1756 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1757 {
1758         sink.short_description(L"Crop a layer.");
1759         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);
1760         sink.para()
1761                 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1762                 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1763                 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1764         sink.definitions()
1765                 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1766                 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1767                 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1768                 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1769         sink.para()->text(L"Examples:");
1770         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");
1771         sink.example(
1772                 L">> MIXER 1-0 CROP\n"
1773                 L"<< 201 MIXER OK\n"
1774                 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1775 }
1776
1777 std::wstring mixer_crop_command(command_context& ctx)
1778 {
1779         if (ctx.parameters.empty())
1780         {
1781                 auto crop = get_current_transform(ctx).image_transform.crop;
1782                 return L"201 MIXER OK\r\n"
1783                         + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1784                         + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1785                         + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1786                         + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1787         }
1788
1789         transforms_applier transforms(ctx);
1790         int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1791         std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1792         double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1793         double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1794         double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1795         double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1796
1797         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1798         {
1799                 transform.image_transform.crop.ul[0] = ul_x;
1800                 transform.image_transform.crop.ul[1] = ul_y;
1801                 transform.image_transform.crop.lr[0] = lr_x;
1802                 transform.image_transform.crop.lr[1] = lr_y;
1803                 return transform;
1804         }, duration, tween));
1805         transforms.apply();
1806
1807         return L"202 MIXER OK\r\n";
1808 }
1809
1810 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1811 {
1812         sink.short_description(L"Rotate a layer.");
1813         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1814         sink.para()
1815                 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1816                 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1817         sink.para()->text(L"Examples:");
1818         sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1819         sink.example(
1820                 L">> MIXER 1-0 ROTATION\n"
1821                 L"<< 201 MIXER OK\n"
1822                 L"<< 45", L"to retrieve the current angle");
1823 }
1824
1825 std::wstring mixer_rotation_command(command_context& ctx)
1826 {
1827         static const double PI = 3.141592653589793;
1828
1829         return single_double_animatable_mixer_command(
1830                         ctx,
1831                         [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1832                         [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1833 }
1834
1835 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1836 {
1837         sink.short_description(L"Adjust the perspective transform of a layer.");
1838         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);
1839         sink.para()
1840                 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1841         sink.definitions()
1842                 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1843                 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1844                 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1845                 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1846         sink.para()->text(L"Examples:");
1847         sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1848         sink.example(
1849                 L">> MIXER 1-10 PERSPECTIVE\n"
1850                 L"<< 201 MIXER OK\n"
1851                 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1852 }
1853
1854 std::wstring mixer_perspective_command(command_context& ctx)
1855 {
1856         if (ctx.parameters.empty())
1857         {
1858                 auto perspective = get_current_transform(ctx).image_transform.perspective;
1859                 return
1860                         L"201 MIXER OK\r\n"
1861                         + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1862                         + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1863                         + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1864                         + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1865                         + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1866                         + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1867                         + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1868                         + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1869         }
1870
1871         transforms_applier transforms(ctx);
1872         int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1873         std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1874         double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1875         double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1876         double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1877         double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1878         double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1879         double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1880         double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1881         double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1882
1883         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1884         {
1885                 transform.image_transform.perspective.ul[0] = ul_x;
1886                 transform.image_transform.perspective.ul[1] = ul_y;
1887                 transform.image_transform.perspective.ur[0] = ur_x;
1888                 transform.image_transform.perspective.ur[1] = ur_y;
1889                 transform.image_transform.perspective.lr[0] = lr_x;
1890                 transform.image_transform.perspective.lr[1] = lr_y;
1891                 transform.image_transform.perspective.ll[0] = ll_x;
1892                 transform.image_transform.perspective.ll[1] = ll_y;
1893                 return transform;
1894         }, duration, tween));
1895         transforms.apply();
1896
1897         return L"202 MIXER OK\r\n";
1898 }
1899
1900 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1901 {
1902         sink.short_description(L"Enable or disable mipmapping for a layer.");
1903         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1904         sink.para()
1905                 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1906                 ->text(L"If no argument is given the current state is returned.");
1907         sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1908         sink.para()->text(L"Examples:");
1909         sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1910         sink.example(
1911                 L">> MIXER 1-10 MIPMAP\n"
1912                 L"<< 201 MIXER OK\n"
1913                 L"<< 1", L"for getting the current state");
1914 }
1915
1916 std::wstring mixer_mipmap_command(command_context& ctx)
1917 {
1918         if (ctx.parameters.empty())
1919                 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1920
1921         transforms_applier transforms(ctx);
1922         bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1923         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1924         {
1925                 transform.image_transform.use_mipmap = value;
1926                 return transform;
1927         }, 0, tweener(L"linear")));
1928         transforms.apply();
1929
1930         return L"202 MIXER OK\r\n";
1931 }
1932
1933 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1934 {
1935         sink.short_description(L"Change the volume of a layer.");
1936         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1937         sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1938         sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1939         sink.para()->text(L"Examples:");
1940         sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1941         sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1942         sink.example(
1943                 L">> MIXER 1-0 VOLUME\n"
1944                 L"<< 201 MIXER OK\n"
1945                 L"<< 0.8", L"to retrieve the current volume");
1946 }
1947
1948 std::wstring mixer_volume_command(command_context& ctx)
1949 {
1950         return single_double_animatable_mixer_command(
1951                 ctx,
1952                 [](const frame_transform& t) { return t.audio_transform.volume; },
1953                 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1954 }
1955
1956 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1957 {
1958         sink.short_description(L"Change the volume of an entire channel.");
1959         sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1960         sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1961         sink.para()->text(L"Examples:");
1962         sink.example(L">> MIXER 1 MASTERVOLUME 0");
1963         sink.example(L">> MIXER 1 MASTERVOLUME 1");
1964         sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1965 }
1966
1967 std::wstring mixer_mastervolume_command(command_context& ctx)
1968 {
1969         if (ctx.parameters.empty())
1970         {
1971                 auto volume = ctx.channel.channel->mixer().get_master_volume();
1972                 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1973         }
1974
1975         float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1976         ctx.channel.channel->mixer().set_master_volume(master_volume);
1977
1978         return L"202 MIXER OK\r\n";
1979 }
1980
1981 void mixer_straight_alpha_describer(core::help_sink& sink, const core::help_repository& repo)
1982 {
1983         sink.short_description(L"Turn straight alpha output on or off for a channel.");
1984         sink.syntax(L"MIXER [video_channel:int] STRAIGHT_ALPHA_OUTPUT {[straight_alpha:0,1|0]}");
1985         sink.para()->text(L"Turn straight alpha output on or off for the specified channel.");
1986         sink.para()->code(L"casparcg.config")->text(L" needs to be configured to enable the feature.");
1987         sink.para()->text(L"Examples:");
1988         sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 0");
1989         sink.example(L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT 1");
1990         sink.example(
1991                         L">> MIXER 1 STRAIGHT_ALPHA_OUTPUT\n"
1992                         L"<< 201 MIXER OK\n"
1993                         L"<< 1");
1994 }
1995
1996 std::wstring mixer_straight_alpha_command(command_context& ctx)
1997 {
1998         if (ctx.parameters.empty())
1999         {
2000                 bool state = ctx.channel.channel->mixer().get_straight_alpha_output();
2001                 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(state) + L"\r\n";
2002         }
2003
2004         bool state = boost::lexical_cast<bool>(ctx.parameters.at(0));
2005         ctx.channel.channel->mixer().set_straight_alpha_output(state);
2006
2007         return L"202 MIXER OK\r\n";
2008 }
2009
2010 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2011 {
2012         sink.short_description(L"Create a grid of video layers.");
2013         sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
2014         sink.para()
2015                 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
2016                 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
2017         sink.para()->text(L"Examples:");
2018         sink.example(L">> MIXER 1 GRID 2");
2019 }
2020
2021 std::wstring mixer_grid_command(command_context& ctx)
2022 {
2023         transforms_applier transforms(ctx);
2024         int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
2025         std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
2026         int n = boost::lexical_cast<int>(ctx.parameters.at(0));
2027         double delta = 1.0 / static_cast<double>(n);
2028         for (int x = 0; x < n; ++x)
2029         {
2030                 for (int y = 0; y < n; ++y)
2031                 {
2032                         int index = x + y*n + 1;
2033                         transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
2034                         {
2035                                 transform.image_transform.fill_translation[0] = x*delta;
2036                                 transform.image_transform.fill_translation[1] = y*delta;
2037                                 transform.image_transform.fill_scale[0] = delta;
2038                                 transform.image_transform.fill_scale[1] = delta;
2039                                 transform.image_transform.clip_translation[0] = x*delta;
2040                                 transform.image_transform.clip_translation[1] = y*delta;
2041                                 transform.image_transform.clip_scale[0] = delta;
2042                                 transform.image_transform.clip_scale[1] = delta;
2043                                 return transform;
2044                         }, duration, tween));
2045                 }
2046         }
2047         transforms.apply();
2048
2049         return L"202 MIXER OK\r\n";
2050 }
2051
2052 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
2053 {
2054         sink.short_description(L"Commit all deferred mixer transforms.");
2055         sink.syntax(L"MIXER [video_channel:int] COMMIT");
2056         sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
2057         sink.para()->text(L"Examples:");
2058         sink.example(
2059                 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
2060                 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
2061                 L">> MIXER 1 COMMIT");
2062 }
2063
2064 std::wstring mixer_commit_command(command_context& ctx)
2065 {
2066         transforms_applier transforms(ctx);
2067         transforms.commit_deferred();
2068
2069         return L"202 MIXER OK\r\n";
2070 }
2071
2072 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
2073 {
2074         sink.short_description(L"Clear all transformations on a channel or layer.");
2075         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
2076         sink.para()->text(L"Clears all transformations on a channel or layer.");
2077         sink.para()->text(L"Examples:");
2078         sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
2079         sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
2080 }
2081
2082 std::wstring mixer_clear_command(command_context& ctx)
2083 {
2084         int layer = ctx.layer_id;
2085
2086         if (layer == -1)
2087                 ctx.channel.channel->stage().clear_transforms();
2088         else
2089                 ctx.channel.channel->stage().clear_transforms(layer);
2090
2091         return L"202 MIXER OK\r\n";
2092 }
2093
2094 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
2095 {
2096         sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
2097         sink.syntax(L"CHANNEL_GRID");
2098         sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
2099         sink.para()
2100                 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
2101                 ->code(L"casparcg.config")->text(L" for this to work correctly.");
2102 }
2103
2104 std::wstring channel_grid_command(command_context& ctx)
2105 {
2106         int index = 1;
2107         auto self = ctx.channels.back();
2108
2109         core::diagnostics::scoped_call_context save;
2110         core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
2111
2112         std::vector<std::wstring> params;
2113         params.push_back(L"SCREEN");
2114         params.push_back(L"0");
2115         params.push_back(L"NAME");
2116         params.push_back(L"Channel Grid Window");
2117         auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage(), get_channels(ctx));
2118
2119         self.channel->output().add(screen);
2120
2121         for (auto& channel : ctx.channels)
2122         {
2123                 if (channel.channel != self.channel)
2124                 {
2125                         core::diagnostics::call_context::for_thread().layer = index;
2126                         auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
2127                         self.channel->stage().load(index, producer, false);
2128                         self.channel->stage().play(index);
2129                         index++;
2130                 }
2131         }
2132
2133         auto num_channels = ctx.channels.size() - 1;
2134         int square_side_length = std::ceil(std::sqrt(num_channels));
2135
2136         ctx.channel_index = self.channel->index();
2137         ctx.channel = self;
2138         ctx.parameters.clear();
2139         ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2140         mixer_grid_command(ctx);
2141
2142         return L"202 CHANNEL_GRID OK\r\n";
2143 }
2144
2145 // Thumbnail Commands
2146
2147 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2148 {
2149         sink.short_description(L"List all thumbnails.");
2150         sink.syntax(L"THUMBNAIL LIST");
2151         sink.para()->text(L"Lists all thumbnails.");
2152         sink.para()->text(L"Examples:");
2153         sink.example(
2154                 L">> THUMBNAIL LIST\n"
2155                 L"<< 200 THUMBNAIL LIST OK\n"
2156                 L"<< \"AMB\" 20130301T124409 1149\n"
2157                 L"<< \"foo/bar\" 20130523T234001 244");
2158 }
2159
2160 std::wstring thumbnail_list_command(command_context& ctx)
2161 {
2162         std::wstringstream replyString;
2163         replyString << L"200 THUMBNAIL LIST OK\r\n";
2164
2165         for (boost::filesystem::recursive_directory_iterator itr(env::thumbnail_folder()), end; itr != end; ++itr)
2166         {
2167                 if (boost::filesystem::is_regular_file(itr->path()))
2168                 {
2169                         if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2170                                 continue;
2171
2172                         auto relativePath = get_relative_without_extension(itr->path(), env::thumbnail_folder());
2173                         auto str = relativePath.generic_wstring();
2174
2175                         if (str[0] == '\\' || str[0] == '/')
2176                                 str = std::wstring(str.begin() + 1, str.end());
2177
2178                         auto mtime = boost::filesystem::last_write_time(itr->path());
2179                         auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2180                         auto file_size = boost::filesystem::file_size(itr->path());
2181
2182                         replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2183                 }
2184         }
2185
2186         replyString << L"\r\n";
2187
2188         return boost::to_upper_copy(replyString.str());
2189 }
2190
2191 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2192 {
2193         sink.short_description(L"Retrieve a thumbnail.");
2194         sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2195         sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2196         sink.para()->text(L"Examples:");
2197         sink.example(
2198                 L">> THUMBNAIL RETRIEVE foo/bar\n"
2199                 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2200                 L"<< ...base64 data...");
2201 }
2202
2203 std::wstring thumbnail_retrieve_command(command_context& ctx)
2204 {
2205         std::wstring filename = env::thumbnail_folder();
2206         filename.append(ctx.parameters.at(0));
2207         filename.append(L".png");
2208
2209         std::wstring file_contents;
2210
2211         auto found_file = find_case_insensitive(filename);
2212
2213         if (found_file)
2214                 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2215
2216         if (file_contents.empty())
2217                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2218
2219         std::wstringstream reply;
2220
2221         reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2222         reply << file_contents;
2223         reply << L"\r\n";
2224         return reply.str();
2225 }
2226
2227 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2228 {
2229         sink.short_description(L"Regenerate a thumbnail.");
2230         sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2231         sink.para()->text(L"Regenerates a thumbnail.");
2232 }
2233
2234 std::wstring thumbnail_generate_command(command_context& ctx)
2235 {
2236         if (ctx.thumb_gen)
2237         {
2238                 ctx.thumb_gen->generate(ctx.parameters.at(0));
2239                 return L"202 THUMBNAIL GENERATE OK\r\n";
2240         }
2241         else
2242                 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2243 }
2244
2245 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2246 {
2247         sink.short_description(L"Regenerate all thumbnails.");
2248         sink.syntax(L"THUMBNAIL GENERATE_ALL");
2249         sink.para()->text(L"Regenerates all thumbnails.");
2250 }
2251
2252 std::wstring thumbnail_generateall_command(command_context& ctx)
2253 {
2254         if (ctx.thumb_gen)
2255         {
2256                 ctx.thumb_gen->generate_all();
2257                 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2258         }
2259         else
2260                 CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
2261 }
2262
2263 // Query Commands
2264
2265 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2266 {
2267         sink.short_description(L"Get information about a media file.");
2268         sink.syntax(L"CINF [filename:string]");
2269         sink.para()->text(L"Returns information about a media file.");
2270 }
2271
2272 std::wstring cinf_command(command_context& ctx)
2273 {
2274         std::wstring info;
2275         for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2276         {
2277                 auto path = itr->path();
2278                 auto file = path.replace_extension(L"").filename().wstring();
2279                 if (boost::iequals(file, ctx.parameters.at(0)))
2280                         info += MediaInfo(itr->path(), ctx.media_info_repo);
2281         }
2282
2283         if (info.empty())
2284                 CASPAR_THROW_EXCEPTION(file_not_found());
2285
2286         std::wstringstream replyString;
2287         replyString << L"200 CINF OK\r\n";
2288         replyString << info << "\r\n";
2289
2290         return replyString.str();
2291 }
2292
2293 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2294 {
2295         sink.short_description(L"List all media files.");
2296         sink.syntax(L"CLS");
2297         sink.para()
2298                 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2299                 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2300 }
2301
2302 std::wstring cls_command(command_context& ctx)
2303 {
2304         std::wstringstream replyString;
2305         replyString << L"200 CLS OK\r\n";
2306         replyString << ListMedia(ctx.media_info_repo);
2307         replyString << L"\r\n";
2308         return boost::to_upper_copy(replyString.str());
2309 }
2310
2311 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2312 {
2313         sink.short_description(L"List all fonts.");
2314         sink.syntax(L"FLS");
2315         sink.para()
2316                 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2317                 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2318         sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2319 }
2320
2321 std::wstring fls_command(command_context& ctx)
2322 {
2323         std::wstringstream replyString;
2324         replyString << L"200 FLS OK\r\n";
2325
2326         for (auto& font : core::text::list_fonts())
2327                 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2328
2329         replyString << L"\r\n";
2330
2331         return replyString.str();
2332 }
2333
2334 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2335 {
2336         sink.short_description(L"List all templates.");
2337         sink.syntax(L"TLS");
2338         sink.para()
2339                 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2340                 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2341 }
2342
2343 std::wstring tls_command(command_context& ctx)
2344 {
2345         std::wstringstream replyString;
2346         replyString << L"200 TLS OK\r\n";
2347
2348         replyString << ListTemplates(ctx.cg_registry);
2349         replyString << L"\r\n";
2350
2351         return replyString.str();
2352 }
2353
2354 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2355 {
2356         sink.short_description(L"Get version information.");
2357         sink.syntax(L"VERSION {[component:string]}");
2358         sink.para()->text(L"Returns the version of specified component.");
2359         sink.para()->text(L"Examples:");
2360         sink.example(
2361                 L">> VERSION\n"
2362                 L"<< 201 VERSION OK\n"
2363                 L"<< 2.1.0.f207a33 STABLE");
2364         sink.example(
2365                 L">> VERSION SERVER\n"
2366                 L"<< 201 VERSION OK\n"
2367                 L"<< 2.1.0.f207a33 STABLE");
2368         sink.example(
2369                 L">> VERSION FLASH\n"
2370                 L"<< 201 VERSION OK\n"
2371                 L"<< 11.8.800.94");
2372         sink.example(
2373                 L">> VERSION TEMPLATEHOST\n"
2374                 L"<< 201 VERSION OK\n"
2375                 L"<< unknown");
2376         sink.example(
2377                 L">> VERSION CEF\n"
2378                 L"<< 201 VERSION OK\n"
2379                 L"<< 3.1750.1805");
2380 }
2381
2382 std::wstring version_command(command_context& ctx)
2383 {
2384         if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2385         {
2386                 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2387
2388                 return L"201 VERSION OK\r\n" + version + L"\r\n";
2389         }
2390
2391         return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2392 }
2393
2394 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2395 {
2396         sink.short_description(L"Get a list of the available channels.");
2397         sink.syntax(L"INFO");
2398         sink.para()->text(L"Retrieves a list of the available channels.");
2399         sink.example(
2400                 L">> INFO\n"
2401                 L"<< 200 INFO OK\n"
2402                 L"<< 1 720p5000 PLAYING\n"
2403                 L"<< 2 PAL PLAYING");
2404 }
2405
2406 std::wstring info_command(command_context& ctx)
2407 {
2408         std::wstringstream replyString;
2409         // This is needed for backwards compatibility with old clients
2410         replyString << L"200 INFO OK\r\n";
2411         for (size_t n = 0; n < ctx.channels.size(); ++n)
2412                 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2413         replyString << L"\r\n";
2414         return replyString.str();
2415 }
2416
2417 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2418 {
2419         std::wstringstream replyString;
2420
2421         if (command.empty())
2422                 replyString << L"201 INFO OK\r\n";
2423         else
2424                 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2425
2426         boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2427         boost::property_tree::xml_parser::write_xml(replyString, info, w);
2428         replyString << L"\r\n";
2429         return replyString.str();
2430 }
2431
2432 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2433 {
2434         sink.short_description(L"Get information about a channel or a layer.");
2435         sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2436         sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2437         sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2438 }
2439
2440 std::wstring info_channel_command(command_context& ctx)
2441 {
2442         boost::property_tree::wptree info;
2443         int layer = ctx.layer_index(std::numeric_limits<int>::min());
2444
2445         if (layer == std::numeric_limits<int>::min())
2446         {
2447                 info.add_child(L"channel", ctx.channel.channel->info())
2448                         .add(L"index", ctx.channel_index);
2449         }
2450         else
2451         {
2452                 if (ctx.parameters.size() >= 1)
2453                 {
2454                         if (boost::iequals(ctx.parameters.at(0), L"B"))
2455                                 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2456                         else
2457                                 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2458                 }
2459                 else
2460                 {
2461                         info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2462                 }
2463         }
2464
2465         return create_info_xml_reply(info);
2466 }
2467
2468 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2469 {
2470         sink.short_description(L"Get information about a template.");
2471         sink.syntax(L"INFO TEMPLATE [template:string]");
2472         sink.para()->text(L"Gets information about the specified template.");
2473 }
2474
2475 std::wstring info_template_command(command_context& ctx)
2476 {
2477         auto filename = ctx.parameters.at(0);
2478
2479         std::wstringstream str;
2480         str << u16(ctx.cg_registry->read_meta_info(filename));
2481         boost::property_tree::wptree info;
2482         boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2483
2484         return create_info_xml_reply(info, L"TEMPLATE");
2485 }
2486
2487 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2488 {
2489         sink.short_description(L"Get the contents of the configuration used.");
2490         sink.syntax(L"INFO CONFIG");
2491         sink.para()->text(L"Gets the contents of the configuration used.");
2492 }
2493
2494 std::wstring info_config_command(command_context& ctx)
2495 {
2496         return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2497 }
2498
2499 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2500 {
2501         sink.short_description(L"Get information about the paths used.");
2502         sink.syntax(L"INFO PATHS");
2503         sink.para()->text(L"Gets information about the paths used.");
2504 }
2505
2506 std::wstring info_paths_command(command_context& ctx)
2507 {
2508         boost::property_tree::wptree info;
2509
2510         info.add(L"paths.media-path",           caspar::env::media_folder());
2511         info.add(L"paths.log-path",                     caspar::env::log_folder());
2512         info.add(L"paths.data-path",                    caspar::env::data_folder());
2513         info.add(L"paths.template-path",                caspar::env::template_folder());
2514         info.add(L"paths.thumbnail-path",       caspar::env::thumbnail_folder());
2515         info.add(L"paths.font-path",                    caspar::env::font_folder());
2516         info.add(L"paths.initial-path",         caspar::env::initial_folder() + L"/");
2517
2518         return create_info_xml_reply(info, L"PATHS");
2519 }
2520
2521 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2522 {
2523         sink.short_description(L"Get system information.");
2524         sink.syntax(L"INFO SYSTEM");
2525         sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2526 }
2527
2528 std::wstring info_system_command(command_context& ctx)
2529 {
2530         boost::property_tree::wptree info;
2531
2532         info.add(L"system.name", caspar::system_product_name());
2533         info.add(L"system.os.description", caspar::os_description());
2534         info.add(L"system.cpu", caspar::cpu_info());
2535
2536         ctx.system_info_repo->fill_information(info);
2537
2538         return create_info_xml_reply(info, L"SYSTEM");
2539 }
2540
2541 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2542 {
2543         sink.short_description(L"Get detailed information about all channels.");
2544         sink.syntax(L"INFO SERVER");
2545         sink.para()->text(L"Gets detailed information about all channels.");
2546 }
2547
2548 std::wstring info_server_command(command_context& ctx)
2549 {
2550         boost::property_tree::wptree info;
2551
2552         int index = 0;
2553         for (auto& channel : ctx.channels)
2554                 info.add_child(L"channels.channel", channel.channel->info())
2555                                 .add(L"index", ++index);
2556
2557         return create_info_xml_reply(info, L"SERVER");
2558 }
2559
2560 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2561 {
2562         sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2563         sink.syntax(L"INFO QUEUES");
2564         sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2565 }
2566
2567 std::wstring info_queues_command(command_context& ctx)
2568 {
2569         return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2570 }
2571
2572 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2573 {
2574         sink.short_description(L"Lists all known threads in the server.");
2575         sink.syntax(L"INFO THREADS");
2576         sink.para()->text(L"Lists all known threads in the server.");
2577 }
2578
2579 std::wstring info_threads_command(command_context& ctx)
2580 {
2581         std::wstringstream replyString;
2582         replyString << L"200 INFO THREADS OK\r\n";
2583
2584         for (auto& thread : get_thread_infos())
2585         {
2586                 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2587         }
2588
2589         replyString << L"\r\n";
2590         return replyString.str();
2591 }
2592
2593 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2594 {
2595         sink.short_description(L"Get the current delay on a channel or a layer.");
2596         sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2597         sink.para()->text(L"Get the current delay on the specified channel or layer.");
2598 }
2599
2600 std::wstring info_delay_command(command_context& ctx)
2601 {
2602         boost::property_tree::wptree info;
2603         auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2604
2605         if (layer == std::numeric_limits<int>::min())
2606                 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2607         else
2608                 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2609                         .add(L"index", layer);
2610
2611         return create_info_xml_reply(info, L"DELAY");
2612 }
2613
2614 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2615 {
2616         sink.short_description(L"Open the diagnostics window.");
2617         sink.syntax(L"DIAG");
2618         sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2619 }
2620
2621 std::wstring diag_command(command_context& ctx)
2622 {
2623         core::diagnostics::osd::show_graphs(true);
2624
2625         return L"202 DIAG OK\r\n";
2626 }
2627
2628 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2629 {
2630         sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2631         sink.syntax(L"GL INFO");
2632         sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2633 }
2634
2635 std::wstring gl_info_command(command_context& ctx)
2636 {
2637         auto device = ctx.ogl_device;
2638
2639         if (!device)
2640                 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2641
2642         std::wstringstream result;
2643         result << L"201 GL INFO OK\r\n";
2644
2645         boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2646         auto info = device->info();
2647         boost::property_tree::write_xml(result, info, w);
2648         result << L"\r\n";
2649
2650         return result.str();
2651 }
2652
2653 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2654 {
2655         sink.short_description(L"Release pooled OpenGL resources.");
2656         sink.syntax(L"GL GC");
2657         sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2658 }
2659
2660 std::wstring gl_gc_command(command_context& ctx)
2661 {
2662         auto device = ctx.ogl_device;
2663
2664         if (!device)
2665                 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2666
2667         device->gc().wait();
2668
2669         return L"202 GL GC OK\r\n";
2670 }
2671
2672 static const int WIDTH = 80;
2673
2674 struct max_width_sink : public core::help_sink
2675 {
2676         std::size_t max_width = 0;
2677
2678         void begin_item(const std::wstring& name) override
2679         {
2680                 max_width = std::max(name.length(), max_width);
2681         };
2682 };
2683
2684 struct short_description_sink : public core::help_sink
2685 {
2686         std::size_t width;
2687         std::wstringstream& out;
2688
2689         short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2690
2691         void begin_item(const std::wstring& name) override
2692         {
2693                 out << std::left << std::setw(width + 1) << name;
2694         };
2695
2696         void short_description(const std::wstring& short_description) override
2697         {
2698                 out << short_description << L"\r\n";
2699         };
2700 };
2701
2702 struct simple_paragraph_builder : core::paragraph_builder
2703 {
2704         std::wostringstream out;
2705         std::wstringstream& commit_to;
2706
2707         simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2708         ~simple_paragraph_builder()
2709         {
2710                 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2711         }
2712         spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2713         {
2714                 out << std::move(text);
2715                 return shared_from_this();
2716         }
2717         spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2718         spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2719         spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2720         spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name)  override { return text(std::move(url)); }
2721 };
2722
2723 struct simple_definition_list_builder : core::definition_list_builder
2724 {
2725         std::wstringstream& out;
2726
2727         simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2728         ~simple_definition_list_builder()
2729         {
2730                 out << L"\n";
2731         }
2732
2733         spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2734         {
2735                 out << core::indent(core::wordwrap(term, WIDTH - 2), L"  ");
2736                 out << core::indent(core::wordwrap(description, WIDTH - 4), L"    ");
2737                 return shared_from_this();
2738         }
2739 };
2740
2741 struct long_description_sink : public core::help_sink
2742 {
2743         std::wstringstream& out;
2744
2745         long_description_sink(std::wstringstream& out) : out(out) { }
2746
2747         void syntax(const std::wstring& syntax) override
2748         {
2749                 out << L"Syntax:\n";
2750                 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L"  ") << L"\n";
2751         };
2752
2753         spl::shared_ptr<core::paragraph_builder> para() override
2754         {
2755                 return spl::make_shared<simple_paragraph_builder>(out);
2756         }
2757
2758         spl::shared_ptr<core::definition_list_builder> definitions() override
2759         {
2760                 return spl::make_shared<simple_definition_list_builder>(out);
2761         }
2762
2763         void example(const std::wstring& code, const std::wstring& caption) override
2764         {
2765                 out << core::indent(core::wordwrap(code, WIDTH - 2), L"  ");
2766
2767                 if (!caption.empty())
2768                         out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L"  ");
2769
2770                 out << L"\n";
2771         }
2772 private:
2773         void begin_item(const std::wstring& name) override
2774         {
2775                 out << name << L"\n\n";
2776         };
2777 };
2778
2779 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2780 {
2781         std::wstringstream result;
2782         result << L"200 " << help_command << L" OK\r\n";
2783         max_width_sink width;
2784         ctx.help_repo->help(tags, width);
2785         short_description_sink sink(width.max_width, result);
2786         sink.width = width.max_width;
2787         ctx.help_repo->help(tags, sink);
2788         result << L"\r\n";
2789         return result.str();
2790 }
2791
2792 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2793 {
2794         std::wstringstream result;
2795         result << L"201 " << help_command << L" OK\r\n";
2796         auto joined = boost::join(ctx.parameters, L" ");
2797         long_description_sink sink(result);
2798         ctx.help_repo->help(tags, joined, sink);
2799         result << L"\r\n";
2800         return result.str();
2801 }
2802
2803 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2804 {
2805         sink.short_description(L"Show online help for AMCP commands.");
2806         sink.syntax(LR"(HELP {[command:string]})");
2807         sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2808         sink.example(L">> HELP", L"Shows a list of commands.");
2809         sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2810 }
2811
2812 std::wstring help_command(command_context& ctx)
2813 {
2814         if (ctx.parameters.size() == 0)
2815                 return create_help_list(L"HELP", ctx, { L"AMCP" });
2816         else
2817                 return create_help_details(L"HELP", ctx, { L"AMCP" });
2818 }
2819
2820 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2821 {
2822         sink.short_description(L"Show online help for producers.");
2823         sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2824         sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2825         sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2826         sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2827 }
2828
2829 std::wstring help_producer_command(command_context& ctx)
2830 {
2831         if (ctx.parameters.size() == 0)
2832                 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2833         else
2834                 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2835 }
2836
2837 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2838 {
2839         sink.short_description(L"Show online help for consumers.");
2840         sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2841         sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2842         sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2843         sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2844 }
2845
2846 std::wstring help_consumer_command(command_context& ctx)
2847 {
2848         if (ctx.parameters.size() == 0)
2849                 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2850         else
2851                 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2852 }
2853
2854 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2855 {
2856         sink.short_description(L"Disconnect the session.");
2857         sink.syntax(L"BYE");
2858         sink.para()
2859                 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2860                 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2861 }
2862
2863 std::wstring bye_command(command_context& ctx)
2864 {
2865         ctx.client->disconnect();
2866         return L"";
2867 }
2868
2869 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2870 {
2871         sink.short_description(L"Shutdown the server.");
2872         sink.syntax(L"KILL");
2873         sink.para()->text(L"Shuts the server down.");
2874 }
2875
2876 std::wstring kill_command(command_context& ctx)
2877 {
2878         ctx.shutdown_server_now.set_value(false);       //false for not attempting to restart
2879         return L"202 KILL OK\r\n";
2880 }
2881
2882 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2883 {
2884         sink.short_description(L"Shutdown the server with restart exit code.");
2885         sink.syntax(L"RESTART");
2886         sink.para()
2887                 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2888                 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2889 }
2890
2891 std::wstring restart_command(command_context& ctx)
2892 {
2893         ctx.shutdown_server_now.set_value(true);        //true for attempting to restart
2894         return L"202 RESTART OK\r\n";
2895 }
2896
2897 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2898 {
2899         sink.short_description(L"Lock or unlock access to a channel.");
2900         sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2901         sink.para()->text(L"Allows for exclusive access to a channel.");
2902         sink.para()->text(L"Examples:");
2903         sink.example(L"LOCK 1 ACQUIRE secret");
2904         sink.example(L"LOCK 1 RELEASE");
2905         sink.example(L"LOCK 1 CLEAR");
2906 }
2907
2908 std::wstring lock_command(command_context& ctx)
2909 {
2910         int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2911         auto lock = ctx.channels.at(channel_index).lock;
2912         auto command = boost::to_upper_copy(ctx.parameters.at(1));
2913
2914         if (command == L"ACQUIRE")
2915         {
2916                 std::wstring lock_phrase = ctx.parameters.at(2);
2917
2918                 //TODO: read options
2919
2920                 //just lock one channel
2921                 if (!lock->try_lock(lock_phrase, ctx.client))
2922                         return L"503 LOCK ACQUIRE FAILED\r\n";
2923
2924                 return L"202 LOCK ACQUIRE OK\r\n";
2925         }
2926         else if (command == L"RELEASE")
2927         {
2928                 lock->release_lock(ctx.client);
2929                 return L"202 LOCK RELEASE OK\r\n";
2930         }
2931         else if (command == L"CLEAR")
2932         {
2933                 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2934                 std::wstring client_override_phrase;
2935
2936                 if (!override_phrase.empty())
2937                         client_override_phrase = ctx.parameters.at(2);
2938
2939                 //just clear one channel
2940                 if (client_override_phrase != override_phrase)
2941                         return L"503 LOCK CLEAR FAILED\r\n";
2942
2943                 lock->clear_locks();
2944
2945                 return L"202 LOCK CLEAR OK\r\n";
2946         }
2947
2948         CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2949 }
2950
2951 void req_describer(core::help_sink& sink, const core::help_repository& repo)
2952 {
2953         sink.short_description(L"Perform any command with an additional request id identifying the response.");
2954         sink.syntax(L"REQ [request_id:string] COMMAND...");
2955         sink.para()
2956                 ->text(L"This special command modifies the AMCP protocol a little bit to prepend ")
2957                 ->code(L"RES request_id")->text(L" to the response, in order to see what asynchronous response matches what request.");
2958         sink.para()->text(L"Examples:");
2959         sink.example(L"REQ unique PLAY 1-0 AMB\n");
2960         sink.example(
2961                 L">> REQ unique PLAY 1-0 AMB\n"
2962                 L"<< RES unique 202 PLAY OK");
2963 }
2964
2965
2966 void register_commands(amcp_command_repository& repo)
2967 {
2968         repo.register_channel_command(  L"Basic Commands",              L"LOADBG",                                              loadbg_describer,                                       loadbg_command,                                 1);
2969         repo.register_channel_command(  L"Basic Commands",              L"LOAD",                                                load_describer,                                         load_command,                                   1);
2970         repo.register_channel_command(  L"Basic Commands",              L"PLAY",                                                play_describer,                                         play_command,                                   0);
2971         repo.register_channel_command(  L"Basic Commands",              L"PAUSE",                                               pause_describer,                                        pause_command,                                  0);
2972         repo.register_channel_command(  L"Basic Commands",              L"RESUME",                                              resume_describer,                                       resume_command,                                 0);
2973         repo.register_channel_command(  L"Basic Commands",              L"STOP",                                                stop_describer,                                         stop_command,                                   0);
2974         repo.register_channel_command(  L"Basic Commands",              L"CLEAR",                                               clear_describer,                                        clear_command,                                  0);
2975         repo.register_channel_command(  L"Basic Commands",              L"CALL",                                                call_describer,                                         call_command,                                   1);
2976         repo.register_channel_command(  L"Basic Commands",              L"SWAP",                                                swap_describer,                                         swap_command,                                   1);
2977         repo.register_channel_command(  L"Basic Commands",              L"ADD",                                                 add_describer,                                          add_command,                                    1);
2978         repo.register_channel_command(  L"Basic Commands",              L"REMOVE",                                              remove_describer,                                       remove_command,                                 0);
2979         repo.register_channel_command(  L"Basic Commands",              L"PRINT",                                               print_describer,                                        print_command,                                  0);
2980         repo.register_command(                  L"Basic Commands",              L"LOG LEVEL",                                   log_level_describer,                            log_level_command,                              1);
2981         repo.register_command(                  L"Basic Commands",              L"LOG CATEGORY",                                log_category_describer,                         log_category_command,                   2);
2982         repo.register_channel_command(  L"Basic Commands",              L"SET",                                                 set_describer,                                          set_command,                                    2);
2983         repo.register_command(                  L"Basic Commands",              L"LOCK",                                                lock_describer,                                         lock_command,                                   2);
2984
2985         repo.register_command(                  L"Data Commands",               L"DATA STORE",                                  data_store_describer,                           data_store_command,                             2);
2986         repo.register_command(                  L"Data Commands",               L"DATA RETRIEVE",                               data_retrieve_describer,                        data_retrieve_command,                  1);
2987         repo.register_command(                  L"Data Commands",               L"DATA LIST",                                   data_list_describer,                            data_list_command,                              0);
2988         repo.register_command(                  L"Data Commands",               L"DATA REMOVE",                                 data_remove_describer,                          data_remove_command,                    1);
2989
2990         repo.register_channel_command(  L"Template Commands",   L"CG ADD",                                              cg_add_describer,                                       cg_add_command,                                 3);
2991         repo.register_channel_command(  L"Template Commands",   L"CG PLAY",                                             cg_play_describer,                                      cg_play_command,                                1);
2992         repo.register_channel_command(  L"Template Commands",   L"CG STOP",                                             cg_stop_describer,                                      cg_stop_command,                                1);
2993         repo.register_channel_command(  L"Template Commands",   L"CG NEXT",                                             cg_next_describer,                                      cg_next_command,                                1);
2994         repo.register_channel_command(  L"Template Commands",   L"CG REMOVE",                                   cg_remove_describer,                            cg_remove_command,                              1);
2995         repo.register_channel_command(  L"Template Commands",   L"CG CLEAR",                                    cg_clear_describer,                                     cg_clear_command,                               0);
2996         repo.register_channel_command(  L"Template Commands",   L"CG UPDATE",                                   cg_update_describer,                            cg_update_command,                              2);
2997         repo.register_channel_command(  L"Template Commands",   L"CG INVOKE",                                   cg_invoke_describer,                            cg_invoke_command,                              2);
2998         repo.register_channel_command(  L"Template Commands",   L"CG INFO",                                             cg_info_describer,                                      cg_info_command,                                0);
2999
3000         repo.register_channel_command(  L"Mixer Commands",              L"MIXER KEYER",                                 mixer_keyer_describer,                          mixer_keyer_command,                    0);
3001         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CHROMA",                                mixer_chroma_describer,                         mixer_chroma_command,                   0);
3002         repo.register_channel_command(  L"Mixer Commands",              L"MIXER BLEND",                                 mixer_blend_describer,                          mixer_blend_command,                    0);
3003         repo.register_channel_command(  L"Mixer Commands",              L"MIXER OPACITY",                               mixer_opacity_describer,                        mixer_opacity_command,                  0);
3004         repo.register_channel_command(  L"Mixer Commands",              L"MIXER BRIGHTNESS",                    mixer_brightness_describer,                     mixer_brightness_command,               0);
3005         repo.register_channel_command(  L"Mixer Commands",              L"MIXER SATURATION",                    mixer_saturation_describer,                     mixer_saturation_command,               0);
3006         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CONTRAST",                              mixer_contrast_describer,                       mixer_contrast_command,                 0);
3007         repo.register_channel_command(  L"Mixer Commands",              L"MIXER LEVELS",                                mixer_levels_describer,                         mixer_levels_command,                   0);
3008         repo.register_channel_command(  L"Mixer Commands",              L"MIXER FILL",                                  mixer_fill_describer,                           mixer_fill_command,                             0);
3009         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CLIP",                                  mixer_clip_describer,                           mixer_clip_command,                             0);
3010         repo.register_channel_command(  L"Mixer Commands",              L"MIXER ANCHOR",                                mixer_anchor_describer,                         mixer_anchor_command,                   0);
3011         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CROP",                                  mixer_crop_describer,                           mixer_crop_command,                             0);
3012         repo.register_channel_command(  L"Mixer Commands",              L"MIXER ROTATION",                              mixer_rotation_describer,                       mixer_rotation_command,                 0);
3013         repo.register_channel_command(  L"Mixer Commands",              L"MIXER PERSPECTIVE",                   mixer_perspective_describer,            mixer_perspective_command,              0);
3014         repo.register_channel_command(  L"Mixer Commands",              L"MIXER MIPMAP",                                mixer_mipmap_describer,                         mixer_mipmap_command,                   0);
3015         repo.register_channel_command(  L"Mixer Commands",              L"MIXER VOLUME",                                mixer_volume_describer,                         mixer_volume_command,                   0);
3016         repo.register_channel_command(  L"Mixer Commands",              L"MIXER MASTERVOLUME",                  mixer_mastervolume_describer,           mixer_mastervolume_command,             0);
3017         repo.register_channel_command(  L"Mixer Commands",              L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer,         mixer_straight_alpha_command,   0);
3018         repo.register_channel_command(  L"Mixer Commands",              L"MIXER GRID",                                  mixer_grid_describer,                           mixer_grid_command,                             1);
3019         repo.register_channel_command(  L"Mixer Commands",              L"MIXER COMMIT",                                mixer_commit_describer,                         mixer_commit_command,                   0);
3020         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CLEAR",                                 mixer_clear_describer,                          mixer_clear_command,                    0);
3021         repo.register_command(                  L"Mixer Commands",              L"CHANNEL_GRID",                                channel_grid_describer,                         channel_grid_command,                   0);
3022
3023         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL LIST",                              thumbnail_list_describer,                       thumbnail_list_command,                 0);
3024         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL RETRIEVE",                  thumbnail_retrieve_describer,           thumbnail_retrieve_command,             1);
3025         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL GENERATE",                  thumbnail_generate_describer,           thumbnail_generate_command,             1);
3026         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL GENERATE_ALL",              thumbnail_generateall_describer,        thumbnail_generateall_command,  0);
3027
3028         repo.register_command(                  L"Query Commands",              L"CINF",                                                cinf_describer,                                         cinf_command,                                   1);
3029         repo.register_command(                  L"Query Commands",              L"CLS",                                                 cls_describer,                                          cls_command,                                    0);
3030         repo.register_command(                  L"Query Commands",              L"FLS",                                                 fls_describer,                                          fls_command,                                    0);
3031         repo.register_command(                  L"Query Commands",              L"TLS",                                                 tls_describer,                                          tls_command,                                    0);
3032         repo.register_command(                  L"Query Commands",              L"VERSION",                                             version_describer,                                      version_command,                                0);
3033         repo.register_command(                  L"Query Commands",              L"INFO",                                                info_describer,                                         info_command,                                   0);
3034         repo.register_channel_command(  L"Query Commands",              L"INFO",                                                info_channel_describer,                         info_channel_command,                   0);
3035         repo.register_command(                  L"Query Commands",              L"INFO TEMPLATE",                               info_template_describer,                        info_template_command,                  1);
3036         repo.register_command(                  L"Query Commands",              L"INFO CONFIG",                                 info_config_describer,                          info_config_command,                    0);
3037         repo.register_command(                  L"Query Commands",              L"INFO PATHS",                                  info_paths_describer,                           info_paths_command,                             0);
3038         repo.register_command(                  L"Query Commands",              L"INFO SYSTEM",                                 info_system_describer,                          info_system_command,                    0);
3039         repo.register_command(                  L"Query Commands",              L"INFO SERVER",                                 info_server_describer,                          info_server_command,                    0);
3040         repo.register_command(                  L"Query Commands",              L"INFO QUEUES",                                 info_queues_describer,                          info_queues_command,                    0);
3041         repo.register_command(                  L"Query Commands",              L"INFO THREADS",                                info_threads_describer,                         info_threads_command,                   0);
3042         repo.register_channel_command(  L"Query Commands",              L"INFO DELAY",                                  info_delay_describer,                           info_delay_command,                             0);
3043         repo.register_command(                  L"Query Commands",              L"DIAG",                                                diag_describer,                                         diag_command,                                   0);
3044         repo.register_command(                  L"Query Commands",              L"GL INFO",                                             gl_info_describer,                                      gl_info_command,                                0);
3045         repo.register_command(                  L"Query Commands",              L"GL GC",                                               gl_gc_describer,                                        gl_gc_command,                                  0);
3046         repo.register_command(                  L"Query Commands",              L"BYE",                                                 bye_describer,                                          bye_command,                                    0);
3047         repo.register_command(                  L"Query Commands",              L"KILL",                                                kill_describer,                                         kill_command,                                   0);
3048         repo.register_command(                  L"Query Commands",              L"RESTART",                                             restart_describer,                                      restart_command,                                0);
3049         repo.register_command(                  L"Query Commands",              L"HELP",                                                help_describer,                                         help_command,                                   0);
3050         repo.register_command(                  L"Query Commands",              L"HELP PRODUCER",                               help_producer_describer,                        help_producer_command,                  0);
3051         repo.register_command(                  L"Query Commands",              L"HELP CONSUMER",                               help_consumer_describer,                        help_consumer_command,                  0);
3052
3053         repo.help_repo()->register_item({ L"AMCP", L"Protocol Commands" }, L"REQ", req_describer);
3054 }
3055
3056 }       //namespace amcp
3057 }}      //namespace caspar