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