]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPCommandsImpl.cpp
8ee1563632a26fb441e1df4dcdea41d06b80d9b9
[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 }
2300
2301 std::wstring cinf_command(command_context& ctx)
2302 {
2303         std::wstring info;
2304         for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2305         {
2306                 auto path = itr->path();
2307                 auto file = path.replace_extension(L"").filename().wstring();
2308                 if (boost::iequals(file, ctx.parameters.at(0)))
2309                         info += MediaInfo(itr->path(), ctx.media_info_repo);
2310         }
2311
2312         if (info.empty())
2313                 CASPAR_THROW_EXCEPTION(file_not_found());
2314
2315         std::wstringstream replyString;
2316         replyString << L"200 CINF OK\r\n";
2317         replyString << info << "\r\n";
2318
2319         return replyString.str();
2320 }
2321
2322 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2323 {
2324         sink.short_description(L"List media files.");
2325         sink.syntax(L"CLS {[sub_directory:string]}");
2326         sink.para()
2327                 ->text(L"Lists media files in the ")->code(L"media")->text(L" folder. Use the command ")
2328                 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2329         sink.para()
2330                 ->text(L"if the optional ")->code(L"sub_directory")
2331                 ->text(L" is specified only the media files in that sub directory will be returned.");
2332 }
2333
2334 std::wstring cls_command(command_context& ctx)
2335 {
2336         std::wstring sub_directory;
2337
2338         if (!ctx.parameters.empty())
2339                 sub_directory = ctx.parameters.at(0);
2340
2341         std::wstringstream replyString;
2342         replyString << L"200 CLS OK\r\n";
2343         replyString << ListMedia(ctx.media_info_repo, sub_directory);
2344         replyString << L"\r\n";
2345         return boost::to_upper_copy(replyString.str());
2346 }
2347
2348 void fls_describer(core::help_sink& sink, const core::help_repository& repo)
2349 {
2350         sink.short_description(L"List all fonts.");
2351         sink.syntax(L"FLS");
2352         sink.para()
2353                 ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
2354                 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
2355         sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
2356 }
2357
2358 std::wstring fls_command(command_context& ctx)
2359 {
2360         std::wstringstream replyString;
2361         replyString << L"200 FLS OK\r\n";
2362
2363         for (auto& font : core::text::list_fonts())
2364                 replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
2365
2366         replyString << L"\r\n";
2367
2368         return replyString.str();
2369 }
2370
2371 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2372 {
2373         sink.short_description(L"List templates.");
2374         sink.syntax(L"TLS {[sub_directory:string]}");
2375         sink.para()
2376                 ->text(L"Lists template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2377                 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2378         sink.para()
2379                 ->text(L"if the optional ")->code(L"sub_directory")
2380                 ->text(L" is specified only the template files in that sub directory will be returned.");
2381 }
2382
2383 std::wstring tls_command(command_context& ctx)
2384 {
2385         std::wstring sub_directory;
2386
2387         if (!ctx.parameters.empty())
2388                 sub_directory = ctx.parameters.at(0);
2389
2390         std::wstringstream replyString;
2391         replyString << L"200 TLS OK\r\n";
2392
2393         replyString << ListTemplates(ctx.cg_registry, sub_directory);
2394         replyString << L"\r\n";
2395
2396         return replyString.str();
2397 }
2398
2399 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2400 {
2401         sink.short_description(L"Get version information.");
2402         sink.syntax(L"VERSION {[component:string]}");
2403         sink.para()->text(L"Returns the version of specified component.");
2404         sink.para()->text(L"Examples:");
2405         sink.example(
2406                 L">> VERSION\n"
2407                 L"<< 201 VERSION OK\n"
2408                 L"<< 2.1.0.f207a33 STABLE");
2409         sink.example(
2410                 L">> VERSION SERVER\n"
2411                 L"<< 201 VERSION OK\n"
2412                 L"<< 2.1.0.f207a33 STABLE");
2413         sink.example(
2414                 L">> VERSION FLASH\n"
2415                 L"<< 201 VERSION OK\n"
2416                 L"<< 11.8.800.94");
2417         sink.example(
2418                 L">> VERSION TEMPLATEHOST\n"
2419                 L"<< 201 VERSION OK\n"
2420                 L"<< unknown");
2421         sink.example(
2422                 L">> VERSION CEF\n"
2423                 L"<< 201 VERSION OK\n"
2424                 L"<< 3.1750.1805");
2425 }
2426
2427 std::wstring version_command(command_context& ctx)
2428 {
2429         if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2430         {
2431                 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2432
2433                 return L"201 VERSION OK\r\n" + version + L"\r\n";
2434         }
2435
2436         return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2437 }
2438
2439 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2440 {
2441         sink.short_description(L"Get a list of the available channels.");
2442         sink.syntax(L"INFO");
2443         sink.para()->text(L"Retrieves a list of the available channels.");
2444         sink.example(
2445                 L">> INFO\n"
2446                 L"<< 200 INFO OK\n"
2447                 L"<< 1 720p5000 PLAYING\n"
2448                 L"<< 2 PAL PLAYING");
2449 }
2450
2451 std::wstring info_command(command_context& ctx)
2452 {
2453         std::wstringstream replyString;
2454         // This is needed for backwards compatibility with old clients
2455         replyString << L"200 INFO OK\r\n";
2456         for (size_t n = 0; n < ctx.channels.size(); ++n)
2457                 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2458         replyString << L"\r\n";
2459         return replyString.str();
2460 }
2461
2462 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2463 {
2464         std::wstringstream replyString;
2465
2466         if (command.empty())
2467                 replyString << L"201 INFO OK\r\n";
2468         else
2469                 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2470
2471         boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2472         boost::property_tree::xml_parser::write_xml(replyString, info, w);
2473         replyString << L"\r\n";
2474         return replyString.str();
2475 }
2476
2477 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2478 {
2479         sink.short_description(L"Get information about a channel or a layer.");
2480         sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2481         sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2482         sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2483 }
2484
2485 std::wstring info_channel_command(command_context& ctx)
2486 {
2487         boost::property_tree::wptree info;
2488         int layer = ctx.layer_index(std::numeric_limits<int>::min());
2489
2490         if (layer == std::numeric_limits<int>::min())
2491         {
2492                 info.add_child(L"channel", ctx.channel.channel->info())
2493                         .add(L"index", ctx.channel_index);
2494         }
2495         else
2496         {
2497                 if (ctx.parameters.size() >= 1)
2498                 {
2499                         if (boost::iequals(ctx.parameters.at(0), L"B"))
2500                                 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2501                         else
2502                                 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2503                 }
2504                 else
2505                 {
2506                         info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2507                 }
2508         }
2509
2510         return create_info_xml_reply(info);
2511 }
2512
2513 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2514 {
2515         sink.short_description(L"Get information about a template.");
2516         sink.syntax(L"INFO TEMPLATE [template:string]");
2517         sink.para()->text(L"Gets information about the specified template.");
2518 }
2519
2520 std::wstring info_template_command(command_context& ctx)
2521 {
2522         auto filename = ctx.parameters.at(0);
2523
2524         std::wstringstream str;
2525         str << u16(ctx.cg_registry->read_meta_info(filename));
2526         boost::property_tree::wptree info;
2527         boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2528
2529         return create_info_xml_reply(info, L"TEMPLATE");
2530 }
2531
2532 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2533 {
2534         sink.short_description(L"Get the contents of the configuration used.");
2535         sink.syntax(L"INFO CONFIG");
2536         sink.para()->text(L"Gets the contents of the configuration used.");
2537 }
2538
2539 std::wstring info_config_command(command_context& ctx)
2540 {
2541         return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2542 }
2543
2544 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2545 {
2546         sink.short_description(L"Get information about the paths used.");
2547         sink.syntax(L"INFO PATHS");
2548         sink.para()->text(L"Gets information about the paths used.");
2549 }
2550
2551 std::wstring info_paths_command(command_context& ctx)
2552 {
2553         boost::property_tree::wptree info;
2554
2555         info.add(L"paths.media-path",           caspar::env::media_folder());
2556         info.add(L"paths.log-path",                     caspar::env::log_folder());
2557         info.add(L"paths.data-path",                    caspar::env::data_folder());
2558         info.add(L"paths.template-path",                caspar::env::template_folder());
2559         info.add(L"paths.thumbnail-path",       caspar::env::thumbnail_folder());
2560         info.add(L"paths.font-path",                    caspar::env::font_folder());
2561         info.add(L"paths.initial-path",         caspar::env::initial_folder() + L"/");
2562
2563         return create_info_xml_reply(info, L"PATHS");
2564 }
2565
2566 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2567 {
2568         sink.short_description(L"Get system information.");
2569         sink.syntax(L"INFO SYSTEM");
2570         sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2571 }
2572
2573 std::wstring info_system_command(command_context& ctx)
2574 {
2575         boost::property_tree::wptree info;
2576
2577         info.add(L"system.name", caspar::system_product_name());
2578         info.add(L"system.os.description", caspar::os_description());
2579         info.add(L"system.cpu", caspar::cpu_info());
2580
2581         ctx.system_info_repo->fill_information(info);
2582
2583         return create_info_xml_reply(info, L"SYSTEM");
2584 }
2585
2586 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2587 {
2588         sink.short_description(L"Get detailed information about all channels.");
2589         sink.syntax(L"INFO SERVER");
2590         sink.para()->text(L"Gets detailed information about all channels.");
2591 }
2592
2593 std::wstring info_server_command(command_context& ctx)
2594 {
2595         boost::property_tree::wptree info;
2596
2597         int index = 0;
2598         for (auto& channel : ctx.channels)
2599                 info.add_child(L"channels.channel", channel.channel->info())
2600                                 .add(L"index", ++index);
2601
2602         return create_info_xml_reply(info, L"SERVER");
2603 }
2604
2605 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2606 {
2607         sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2608         sink.syntax(L"INFO QUEUES");
2609         sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2610 }
2611
2612 std::wstring info_queues_command(command_context& ctx)
2613 {
2614         return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2615 }
2616
2617 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2618 {
2619         sink.short_description(L"Lists all known threads in the server.");
2620         sink.syntax(L"INFO THREADS");
2621         sink.para()->text(L"Lists all known threads in the server.");
2622 }
2623
2624 std::wstring info_threads_command(command_context& ctx)
2625 {
2626         std::wstringstream replyString;
2627         replyString << L"200 INFO THREADS OK\r\n";
2628
2629         for (auto& thread : get_thread_infos())
2630         {
2631                 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2632         }
2633
2634         replyString << L"\r\n";
2635         return replyString.str();
2636 }
2637
2638 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2639 {
2640         sink.short_description(L"Get the current delay on a channel or a layer.");
2641         sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2642         sink.para()->text(L"Get the current delay on the specified channel or layer.");
2643 }
2644
2645 std::wstring info_delay_command(command_context& ctx)
2646 {
2647         boost::property_tree::wptree info;
2648         auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2649
2650         if (layer == std::numeric_limits<int>::min())
2651                 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2652         else
2653                 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2654                         .add(L"index", layer);
2655
2656         return create_info_xml_reply(info, L"DELAY");
2657 }
2658
2659 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2660 {
2661         sink.short_description(L"Open the diagnostics window.");
2662         sink.syntax(L"DIAG");
2663         sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2664 }
2665
2666 std::wstring diag_command(command_context& ctx)
2667 {
2668         core::diagnostics::osd::show_graphs(true);
2669
2670         return L"202 DIAG OK\r\n";
2671 }
2672
2673 void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
2674 {
2675         sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
2676         sink.syntax(L"GL INFO");
2677         sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
2678 }
2679
2680 std::wstring gl_info_command(command_context& ctx)
2681 {
2682         auto device = ctx.ogl_device;
2683
2684         if (!device)
2685                 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2686
2687         std::wstringstream result;
2688         result << L"201 GL INFO OK\r\n";
2689
2690         boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2691         auto info = device->info();
2692         boost::property_tree::write_xml(result, info, w);
2693         result << L"\r\n";
2694
2695         return result.str();
2696 }
2697
2698 void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
2699 {
2700         sink.short_description(L"Release pooled OpenGL resources.");
2701         sink.syntax(L"GL GC");
2702         sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
2703 }
2704
2705 std::wstring gl_gc_command(command_context& ctx)
2706 {
2707         auto device = ctx.ogl_device;
2708
2709         if (!device)
2710                 CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
2711
2712         device->gc().wait();
2713
2714         return L"202 GL GC OK\r\n";
2715 }
2716
2717 static const int WIDTH = 80;
2718
2719 struct max_width_sink : public core::help_sink
2720 {
2721         std::size_t max_width = 0;
2722
2723         void begin_item(const std::wstring& name) override
2724         {
2725                 max_width = std::max(name.length(), max_width);
2726         };
2727 };
2728
2729 struct short_description_sink : public core::help_sink
2730 {
2731         std::size_t width;
2732         std::wstringstream& out;
2733
2734         short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2735
2736         void begin_item(const std::wstring& name) override
2737         {
2738                 out << std::left << std::setw(width + 1) << name;
2739         };
2740
2741         void short_description(const std::wstring& short_description) override
2742         {
2743                 out << short_description << L"\r\n";
2744         };
2745 };
2746
2747 struct simple_paragraph_builder : core::paragraph_builder
2748 {
2749         std::wostringstream out;
2750         std::wstringstream& commit_to;
2751
2752         simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2753         ~simple_paragraph_builder()
2754         {
2755                 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2756         }
2757         spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2758         {
2759                 out << std::move(text);
2760                 return shared_from_this();
2761         }
2762         spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2763         spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
2764         spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2765         spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name)  override { return text(std::move(url)); }
2766 };
2767
2768 struct simple_definition_list_builder : core::definition_list_builder
2769 {
2770         std::wstringstream& out;
2771
2772         simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2773         ~simple_definition_list_builder()
2774         {
2775                 out << L"\n";
2776         }
2777
2778         spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2779         {
2780                 out << core::indent(core::wordwrap(term, WIDTH - 2), L"  ");
2781                 out << core::indent(core::wordwrap(description, WIDTH - 4), L"    ");
2782                 return shared_from_this();
2783         }
2784 };
2785
2786 struct long_description_sink : public core::help_sink
2787 {
2788         std::wstringstream& out;
2789
2790         long_description_sink(std::wstringstream& out) : out(out) { }
2791
2792         void syntax(const std::wstring& syntax) override
2793         {
2794                 out << L"Syntax:\n";
2795                 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L"  ") << L"\n";
2796         };
2797
2798         spl::shared_ptr<core::paragraph_builder> para() override
2799         {
2800                 return spl::make_shared<simple_paragraph_builder>(out);
2801         }
2802
2803         spl::shared_ptr<core::definition_list_builder> definitions() override
2804         {
2805                 return spl::make_shared<simple_definition_list_builder>(out);
2806         }
2807
2808         void example(const std::wstring& code, const std::wstring& caption) override
2809         {
2810                 out << core::indent(core::wordwrap(code, WIDTH - 2), L"  ");
2811
2812                 if (!caption.empty())
2813                         out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L"  ");
2814
2815                 out << L"\n";
2816         }
2817 private:
2818         void begin_item(const std::wstring& name) override
2819         {
2820                 out << name << L"\n\n";
2821         };
2822 };
2823
2824 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2825 {
2826         std::wstringstream result;
2827         result << L"200 " << help_command << L" OK\r\n";
2828         max_width_sink width;
2829         ctx.help_repo->help(tags, width);
2830         short_description_sink sink(width.max_width, result);
2831         sink.width = width.max_width;
2832         ctx.help_repo->help(tags, sink);
2833         result << L"\r\n";
2834         return result.str();
2835 }
2836
2837 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2838 {
2839         std::wstringstream result;
2840         result << L"201 " << help_command << L" OK\r\n";
2841         auto joined = boost::join(ctx.parameters, L" ");
2842         long_description_sink sink(result);
2843         ctx.help_repo->help(tags, joined, sink);
2844         result << L"\r\n";
2845         return result.str();
2846 }
2847
2848 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2849 {
2850         sink.short_description(L"Show online help for AMCP commands.");
2851         sink.syntax(LR"(HELP {[command:string]})");
2852         sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2853         sink.example(L">> HELP", L"Shows a list of commands.");
2854         sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2855 }
2856
2857 std::wstring help_command(command_context& ctx)
2858 {
2859         if (ctx.parameters.size() == 0)
2860                 return create_help_list(L"HELP", ctx, { L"AMCP" });
2861         else
2862                 return create_help_details(L"HELP", ctx, { L"AMCP" });
2863 }
2864
2865 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2866 {
2867         sink.short_description(L"Show online help for producers.");
2868         sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2869         sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2870         sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2871         sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2872 }
2873
2874 std::wstring help_producer_command(command_context& ctx)
2875 {
2876         if (ctx.parameters.size() == 0)
2877                 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2878         else
2879                 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2880 }
2881
2882 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2883 {
2884         sink.short_description(L"Show online help for consumers.");
2885         sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2886         sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2887         sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2888         sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2889 }
2890
2891 std::wstring help_consumer_command(command_context& ctx)
2892 {
2893         if (ctx.parameters.size() == 0)
2894                 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2895         else
2896                 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2897 }
2898
2899 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2900 {
2901         sink.short_description(L"Disconnect the session.");
2902         sink.syntax(L"BYE");
2903         sink.para()
2904                 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2905                 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2906 }
2907
2908 std::wstring bye_command(command_context& ctx)
2909 {
2910         ctx.client->disconnect();
2911         return L"";
2912 }
2913
2914 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2915 {
2916         sink.short_description(L"Shutdown the server.");
2917         sink.syntax(L"KILL");
2918         sink.para()->text(L"Shuts the server down.");
2919 }
2920
2921 std::wstring kill_command(command_context& ctx)
2922 {
2923         ctx.shutdown_server_now.set_value(false);       //false for not attempting to restart
2924         return L"202 KILL OK\r\n";
2925 }
2926
2927 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2928 {
2929         sink.short_description(L"Shutdown the server with restart exit code.");
2930         sink.syntax(L"RESTART");
2931         sink.para()
2932                 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2933                 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2934 }
2935
2936 std::wstring restart_command(command_context& ctx)
2937 {
2938         ctx.shutdown_server_now.set_value(true);        //true for attempting to restart
2939         return L"202 RESTART OK\r\n";
2940 }
2941
2942 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2943 {
2944         sink.short_description(L"Lock or unlock access to a channel.");
2945         sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2946         sink.para()->text(L"Allows for exclusive access to a channel.");
2947         sink.para()->text(L"Examples:");
2948         sink.example(L"LOCK 1 ACQUIRE secret");
2949         sink.example(L"LOCK 1 RELEASE");
2950         sink.example(L"LOCK 1 CLEAR");
2951 }
2952
2953 std::wstring lock_command(command_context& ctx)
2954 {
2955         int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2956         auto lock = ctx.channels.at(channel_index).lock;
2957         auto command = boost::to_upper_copy(ctx.parameters.at(1));
2958
2959         if (command == L"ACQUIRE")
2960         {
2961                 std::wstring lock_phrase = ctx.parameters.at(2);
2962
2963                 //TODO: read options
2964
2965                 //just lock one channel
2966                 if (!lock->try_lock(lock_phrase, ctx.client))
2967                         return L"503 LOCK ACQUIRE FAILED\r\n";
2968
2969                 return L"202 LOCK ACQUIRE OK\r\n";
2970         }
2971         else if (command == L"RELEASE")
2972         {
2973                 lock->release_lock(ctx.client);
2974                 return L"202 LOCK RELEASE OK\r\n";
2975         }
2976         else if (command == L"CLEAR")
2977         {
2978                 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2979                 std::wstring client_override_phrase;
2980
2981                 if (!override_phrase.empty())
2982                         client_override_phrase = ctx.parameters.at(2);
2983
2984                 //just clear one channel
2985                 if (client_override_phrase != override_phrase)
2986                         return L"503 LOCK CLEAR FAILED\r\n";
2987
2988                 lock->clear_locks();
2989
2990                 return L"202 LOCK CLEAR OK\r\n";
2991         }
2992
2993         CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2994 }
2995
2996 void req_describer(core::help_sink& sink, const core::help_repository& repo)
2997 {
2998         sink.short_description(L"Perform any command with an additional request id identifying the response.");
2999         sink.syntax(L"REQ [request_id:string] COMMAND...");
3000         sink.para()
3001                 ->text(L"This special command modifies the AMCP protocol a little bit to prepend ")
3002                 ->code(L"RES request_id")->text(L" to the response, in order to see what asynchronous response matches what request.");
3003         sink.para()->text(L"Examples:");
3004         sink.example(L"REQ unique PLAY 1-0 AMB\n");
3005         sink.example(
3006                 L">> REQ unique PLAY 1-0 AMB\n"
3007                 L"<< RES unique 202 PLAY OK");
3008 }
3009
3010
3011 void register_commands(amcp_command_repository& repo)
3012 {
3013         repo.register_channel_command(  L"Basic Commands",              L"LOADBG",                                              loadbg_describer,                                       loadbg_command,                                 1);
3014         repo.register_channel_command(  L"Basic Commands",              L"LOAD",                                                load_describer,                                         load_command,                                   1);
3015         repo.register_channel_command(  L"Basic Commands",              L"PLAY",                                                play_describer,                                         play_command,                                   0);
3016         repo.register_channel_command(  L"Basic Commands",              L"PAUSE",                                               pause_describer,                                        pause_command,                                  0);
3017         repo.register_channel_command(  L"Basic Commands",              L"RESUME",                                              resume_describer,                                       resume_command,                                 0);
3018         repo.register_channel_command(  L"Basic Commands",              L"STOP",                                                stop_describer,                                         stop_command,                                   0);
3019         repo.register_channel_command(  L"Basic Commands",              L"CLEAR",                                               clear_describer,                                        clear_command,                                  0);
3020         repo.register_channel_command(  L"Basic Commands",              L"CALL",                                                call_describer,                                         call_command,                                   1);
3021         repo.register_channel_command(  L"Basic Commands",              L"SWAP",                                                swap_describer,                                         swap_command,                                   1);
3022         repo.register_channel_command(  L"Basic Commands",              L"ADD",                                                 add_describer,                                          add_command,                                    1);
3023         repo.register_channel_command(  L"Basic Commands",              L"REMOVE",                                              remove_describer,                                       remove_command,                                 0);
3024         repo.register_channel_command(  L"Basic Commands",              L"PRINT",                                               print_describer,                                        print_command,                                  0);
3025         repo.register_command(                  L"Basic Commands",              L"LOG LEVEL",                                   log_level_describer,                            log_level_command,                              1);
3026         repo.register_command(                  L"Basic Commands",              L"LOG CATEGORY",                                log_category_describer,                         log_category_command,                   2);
3027         repo.register_channel_command(  L"Basic Commands",              L"SET",                                                 set_describer,                                          set_command,                                    2);
3028         repo.register_command(                  L"Basic Commands",              L"LOCK",                                                lock_describer,                                         lock_command,                                   2);
3029
3030         repo.register_command(                  L"Data Commands",               L"DATA STORE",                                  data_store_describer,                           data_store_command,                             2);
3031         repo.register_command(                  L"Data Commands",               L"DATA RETRIEVE",                               data_retrieve_describer,                        data_retrieve_command,                  1);
3032         repo.register_command(                  L"Data Commands",               L"DATA LIST",                                   data_list_describer,                            data_list_command,                              0);
3033         repo.register_command(                  L"Data Commands",               L"DATA REMOVE",                                 data_remove_describer,                          data_remove_command,                    1);
3034
3035         repo.register_channel_command(  L"Template Commands",   L"CG ADD",                                              cg_add_describer,                                       cg_add_command,                                 3);
3036         repo.register_channel_command(  L"Template Commands",   L"CG PLAY",                                             cg_play_describer,                                      cg_play_command,                                1);
3037         repo.register_channel_command(  L"Template Commands",   L"CG STOP",                                             cg_stop_describer,                                      cg_stop_command,                                1);
3038         repo.register_channel_command(  L"Template Commands",   L"CG NEXT",                                             cg_next_describer,                                      cg_next_command,                                1);
3039         repo.register_channel_command(  L"Template Commands",   L"CG REMOVE",                                   cg_remove_describer,                            cg_remove_command,                              1);
3040         repo.register_channel_command(  L"Template Commands",   L"CG CLEAR",                                    cg_clear_describer,                                     cg_clear_command,                               0);
3041         repo.register_channel_command(  L"Template Commands",   L"CG UPDATE",                                   cg_update_describer,                            cg_update_command,                              2);
3042         repo.register_channel_command(  L"Template Commands",   L"CG INVOKE",                                   cg_invoke_describer,                            cg_invoke_command,                              2);
3043         repo.register_channel_command(  L"Template Commands",   L"CG INFO",                                             cg_info_describer,                                      cg_info_command,                                0);
3044
3045         repo.register_channel_command(  L"Mixer Commands",              L"MIXER KEYER",                                 mixer_keyer_describer,                          mixer_keyer_command,                    0);
3046         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CHROMA",                                mixer_chroma_describer,                         mixer_chroma_command,                   0);
3047         repo.register_channel_command(  L"Mixer Commands",              L"MIXER BLEND",                                 mixer_blend_describer,                          mixer_blend_command,                    0);
3048         repo.register_channel_command(  L"Mixer Commands",              L"MIXER OPACITY",                               mixer_opacity_describer,                        mixer_opacity_command,                  0);
3049         repo.register_channel_command(  L"Mixer Commands",              L"MIXER BRIGHTNESS",                    mixer_brightness_describer,                     mixer_brightness_command,               0);
3050         repo.register_channel_command(  L"Mixer Commands",              L"MIXER SATURATION",                    mixer_saturation_describer,                     mixer_saturation_command,               0);
3051         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CONTRAST",                              mixer_contrast_describer,                       mixer_contrast_command,                 0);
3052         repo.register_channel_command(  L"Mixer Commands",              L"MIXER LEVELS",                                mixer_levels_describer,                         mixer_levels_command,                   0);
3053         repo.register_channel_command(  L"Mixer Commands",              L"MIXER FILL",                                  mixer_fill_describer,                           mixer_fill_command,                             0);
3054         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CLIP",                                  mixer_clip_describer,                           mixer_clip_command,                             0);
3055         repo.register_channel_command(  L"Mixer Commands",              L"MIXER ANCHOR",                                mixer_anchor_describer,                         mixer_anchor_command,                   0);
3056         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CROP",                                  mixer_crop_describer,                           mixer_crop_command,                             0);
3057         repo.register_channel_command(  L"Mixer Commands",              L"MIXER ROTATION",                              mixer_rotation_describer,                       mixer_rotation_command,                 0);
3058         repo.register_channel_command(  L"Mixer Commands",              L"MIXER PERSPECTIVE",                   mixer_perspective_describer,            mixer_perspective_command,              0);
3059         repo.register_channel_command(  L"Mixer Commands",              L"MIXER MIPMAP",                                mixer_mipmap_describer,                         mixer_mipmap_command,                   0);
3060         repo.register_channel_command(  L"Mixer Commands",              L"MIXER VOLUME",                                mixer_volume_describer,                         mixer_volume_command,                   0);
3061         repo.register_channel_command(  L"Mixer Commands",              L"MIXER MASTERVOLUME",                  mixer_mastervolume_describer,           mixer_mastervolume_command,             0);
3062         repo.register_channel_command(  L"Mixer Commands",              L"MIXER STRAIGHT_ALPHA_OUTPUT", mixer_straight_alpha_describer,         mixer_straight_alpha_command,   0);
3063         repo.register_channel_command(  L"Mixer Commands",              L"MIXER GRID",                                  mixer_grid_describer,                           mixer_grid_command,                             1);
3064         repo.register_channel_command(  L"Mixer Commands",              L"MIXER COMMIT",                                mixer_commit_describer,                         mixer_commit_command,                   0);
3065         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CLEAR",                                 mixer_clear_describer,                          mixer_clear_command,                    0);
3066         repo.register_command(                  L"Mixer Commands",              L"CHANNEL_GRID",                                channel_grid_describer,                         channel_grid_command,                   0);
3067
3068         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL LIST",                              thumbnail_list_describer,                       thumbnail_list_command,                 0);
3069         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL RETRIEVE",                  thumbnail_retrieve_describer,           thumbnail_retrieve_command,             1);
3070         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL GENERATE",                  thumbnail_generate_describer,           thumbnail_generate_command,             1);
3071         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL GENERATE_ALL",              thumbnail_generateall_describer,        thumbnail_generateall_command,  0);
3072
3073         repo.register_command(                  L"Query Commands",              L"CINF",                                                cinf_describer,                                         cinf_command,                                   1);
3074         repo.register_command(                  L"Query Commands",              L"CLS",                                                 cls_describer,                                          cls_command,                                    0);
3075         repo.register_command(                  L"Query Commands",              L"FLS",                                                 fls_describer,                                          fls_command,                                    0);
3076         repo.register_command(                  L"Query Commands",              L"TLS",                                                 tls_describer,                                          tls_command,                                    0);
3077         repo.register_command(                  L"Query Commands",              L"VERSION",                                             version_describer,                                      version_command,                                0);
3078         repo.register_command(                  L"Query Commands",              L"INFO",                                                info_describer,                                         info_command,                                   0);
3079         repo.register_channel_command(  L"Query Commands",              L"INFO",                                                info_channel_describer,                         info_channel_command,                   0);
3080         repo.register_command(                  L"Query Commands",              L"INFO TEMPLATE",                               info_template_describer,                        info_template_command,                  1);
3081         repo.register_command(                  L"Query Commands",              L"INFO CONFIG",                                 info_config_describer,                          info_config_command,                    0);
3082         repo.register_command(                  L"Query Commands",              L"INFO PATHS",                                  info_paths_describer,                           info_paths_command,                             0);
3083         repo.register_command(                  L"Query Commands",              L"INFO SYSTEM",                                 info_system_describer,                          info_system_command,                    0);
3084         repo.register_command(                  L"Query Commands",              L"INFO SERVER",                                 info_server_describer,                          info_server_command,                    0);
3085         repo.register_command(                  L"Query Commands",              L"INFO QUEUES",                                 info_queues_describer,                          info_queues_command,                    0);
3086         repo.register_command(                  L"Query Commands",              L"INFO THREADS",                                info_threads_describer,                         info_threads_command,                   0);
3087         repo.register_channel_command(  L"Query Commands",              L"INFO DELAY",                                  info_delay_describer,                           info_delay_command,                             0);
3088         repo.register_command(                  L"Query Commands",              L"DIAG",                                                diag_describer,                                         diag_command,                                   0);
3089         repo.register_command(                  L"Query Commands",              L"GL INFO",                                             gl_info_describer,                                      gl_info_command,                                0);
3090         repo.register_command(                  L"Query Commands",              L"GL GC",                                               gl_gc_describer,                                        gl_gc_command,                                  0);
3091         repo.register_command(                  L"Query Commands",              L"BYE",                                                 bye_describer,                                          bye_command,                                    0);
3092         repo.register_command(                  L"Query Commands",              L"KILL",                                                kill_describer,                                         kill_command,                                   0);
3093         repo.register_command(                  L"Query Commands",              L"RESTART",                                             restart_describer,                                      restart_command,                                0);
3094         repo.register_command(                  L"Query Commands",              L"HELP",                                                help_describer,                                         help_command,                                   0);
3095         repo.register_command(                  L"Query Commands",              L"HELP PRODUCER",                               help_producer_describer,                        help_producer_command,                  0);
3096         repo.register_command(                  L"Query Commands",              L"HELP CONSUMER",                               help_consumer_describer,                        help_consumer_command,                  0);
3097
3098         repo.help_repo()->register_item({ L"AMCP", L"Protocol Commands" }, L"REQ", req_describer);
3099 }
3100
3101 }       //namespace amcp
3102 }}      //namespace caspar