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