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