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