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