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