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