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