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