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