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