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