]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPCommandsImpl.cpp
* Changed usage of BOOST_THROW_EXCEPTION to CASPAR_THROW_EXCEPTION in all places...
[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 spl::shared_ptr<core::cg_proxy> get_expected_cg_proxy(command_context& ctx)
984 {
985         auto proxy = ctx.cg_registry->get_proxy(spl::make_shared_ptr(ctx.channel.channel), ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
986
987         if (proxy == cg_proxy::empty())
988                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"No CG proxy running on layer"));
989
990         return proxy;
991 }
992
993 void cg_stop_describer(core::help_sink& sink, const core::help_repository& repo)
994 {
995         sink.short_description(L"Stop and remove a template.");
996         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} STOP [cg_layer:int]");
997         sink.para()
998                 ->text(L"Stops and removes the template from the specified layer. This is different from ")->code(L"REMOVE")
999                 ->text(L" in that the template gets a chance to animate out when it is stopped.");
1000         sink.para()->text(L"Examples:");
1001         sink.example(L"CG 1 STOP 0");
1002 }
1003
1004 std::wstring cg_stop_command(command_context& ctx)
1005 {
1006         int layer = get_and_validate_layer(ctx.parameters.at(0));
1007         get_expected_cg_proxy(ctx)->stop(layer, 0);
1008
1009         return L"202 CG OK\r\n";
1010 }
1011
1012 void cg_next_describer(core::help_sink& sink, const core::help_repository& repo)
1013 {
1014         sink.short_description(LR"(Trigger a "continue" in a template.)");
1015         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} NEXT [cg_layer:int]");
1016         sink.para()
1017                 ->text(LR"(Triggers a "continue" in the template on the specified layer. )")
1018                 ->text(L"This is used to control animations that has multiple discreet steps.");
1019         sink.para()->text(L"Examples:");
1020         sink.example(L"CG 1 NEXT 0");
1021 }
1022
1023 std::wstring cg_next_command(command_context& ctx)
1024 {
1025         int layer = get_and_validate_layer(ctx.parameters.at(0));
1026         get_expected_cg_proxy(ctx)->next(layer);
1027
1028         return L"202 CG OK\r\n";
1029 }
1030
1031 void cg_remove_describer(core::help_sink& sink, const core::help_repository& repo)
1032 {
1033         sink.short_description(L"Remove a template.");
1034         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} REMOVE [cg_layer:int]");
1035         sink.para()->text(L"Removes the template from the specified layer.");
1036         sink.para()->text(L"Examples:");
1037         sink.example(L"CG 1 REMOVE 0");
1038 }
1039
1040 std::wstring cg_remove_command(command_context& ctx)
1041 {
1042         int layer = get_and_validate_layer(ctx.parameters.at(0));
1043         get_expected_cg_proxy(ctx)->remove(layer);
1044
1045         return L"202 CG OK\r\n";
1046 }
1047
1048 void cg_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1049 {
1050         sink.short_description(L"Remove all templates on a video layer.");
1051         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} CLEAR");
1052         sink.para()->text(L"Removes all templates on a video layer. The entire cg producer will be removed.");
1053         sink.para()->text(L"Examples:");
1054         sink.example(L"CG 1 CLEAR");
1055 }
1056
1057 std::wstring cg_clear_command(command_context& ctx)
1058 {
1059         ctx.channel.channel->stage().clear(ctx.layer_index(core::cg_proxy::DEFAULT_LAYER));
1060
1061         return L"202 CG OK\r\n";
1062 }
1063
1064 void cg_update_describer(core::help_sink& sink, const core::help_repository& repo)
1065 {
1066         sink.short_description(L"Update a template with new data.");
1067         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} UPDATE [cg_layer:int] [data:string]");
1068         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.");
1069 }
1070
1071 std::wstring cg_update_command(command_context& ctx)
1072 {
1073         int layer = get_and_validate_layer(ctx.parameters.at(0));
1074
1075         std::wstring dataString = ctx.parameters.at(1);
1076         if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1077         {
1078                 //The data is not XML or Json, it must be a filename
1079                 std::wstring filename = env::data_folder();
1080                 filename.append(dataString);
1081                 filename.append(L".ftd");
1082
1083                 dataString = read_file(boost::filesystem::path(filename));
1084         }
1085
1086         get_expected_cg_proxy(ctx)->update(layer, dataString);
1087
1088         return L"202 CG OK\r\n";
1089 }
1090
1091 void cg_invoke_describer(core::help_sink& sink, const core::help_repository& repo)
1092 {
1093         sink.short_description(L"Invoke a method/label on a template.");
1094         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INVOKE [cg_layer:int] [method:string]");
1095         sink.para()->text(L"Invokes the given method on the template on the specified layer.");
1096         sink.para()->text(L"Can be used to jump the playhead to a specific label.");
1097 }
1098
1099 std::wstring cg_invoke_command(command_context& ctx)
1100 {
1101         std::wstringstream replyString;
1102         replyString << L"201 CG OK\r\n";
1103         int layer = get_and_validate_layer(ctx.parameters.at(0));
1104         auto result = get_expected_cg_proxy(ctx)->invoke(layer, ctx.parameters.at(1));
1105         replyString << result << L"\r\n";
1106
1107         return replyString.str();
1108 }
1109
1110 void cg_info_describer(core::help_sink& sink, const core::help_repository& repo)
1111 {
1112         sink.short_description(L"Get information about a running template or the template host.");
1113         sink.syntax(L"CG [video_channel:int]{-[layer:int]|-9999} INFO {[cg_layer:int]}");
1114         sink.para()->text(L"Retrieves information about the template on the specified layer.");
1115         sink.para()->text(L"If ")->code(L"cg_layer")->text(L" is not given, information about the template host is given instead.");
1116 }
1117
1118 std::wstring cg_info_command(command_context& ctx)
1119 {
1120         std::wstringstream replyString;
1121         replyString << L"201 CG OK\r\n";
1122
1123         if (ctx.parameters.empty())
1124         {
1125                 auto info = get_expected_cg_proxy(ctx)->template_host_info();
1126                 replyString << info << L"\r\n";
1127         }
1128         else
1129         {
1130                 int layer = get_and_validate_layer(ctx.parameters.at(0));
1131                 auto desc = get_expected_cg_proxy(ctx)->description(layer);
1132
1133                 replyString << desc << L"\r\n";
1134         }
1135
1136         return replyString.str();
1137 }
1138
1139 // Mixer Commands
1140
1141 core::frame_transform get_current_transform(command_context& ctx)
1142 {
1143         return ctx.channel.channel->stage().get_current_transform(ctx.layer_index()).get();
1144 }
1145
1146 template<typename Func>
1147 std::wstring reply_value(command_context& ctx, const Func& extractor)
1148 {
1149         auto value = extractor(get_current_transform(ctx));
1150
1151         return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(value)+L"\r\n";
1152 }
1153
1154 class transforms_applier
1155 {
1156         static tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms_;
1157
1158         std::vector<stage::transform_tuple_t>   transforms_;
1159         command_context&                                                ctx_;
1160         bool                                                                    defer_;
1161 public:
1162         transforms_applier(command_context& ctx)
1163                 : ctx_(ctx)
1164         {
1165                 defer_ = !ctx.parameters.empty() && boost::iequals(ctx.parameters.back(), L"DEFER");
1166
1167                 if (defer_)
1168                         ctx.parameters.pop_back();
1169         }
1170
1171         void add(stage::transform_tuple_t&& transform)
1172         {
1173                 transforms_.push_back(std::move(transform));
1174         }
1175
1176         void commit_deferred()
1177         {
1178                 ctx_.channel.channel->stage().apply_transforms(
1179                                 std::move(deferred_transforms_[ctx_.channel_index]));
1180         }
1181
1182         void apply()
1183         {
1184                 if (defer_)
1185                 {
1186                         auto& defer_tranforms = deferred_transforms_[ctx_.channel_index];
1187                         defer_tranforms.insert(defer_tranforms.end(), transforms_.begin(), transforms_.end());
1188                 }
1189                 else
1190                         ctx_.channel.channel->stage().apply_transforms(transforms_);
1191         }
1192 };
1193 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> transforms_applier::deferred_transforms_;
1194
1195 void mixer_keyer_describer(core::help_sink& sink, const core::help_repository& repo)
1196 {
1197         sink.short_description(L"Let a layer act as alpha for the one obove.");
1198         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} KEYER {keyer:0,1|0}");
1199         sink.para()
1200                 ->text(L"Replaces layer ")->code(L"n+1")->text(L"'s alpha with the ")
1201                 ->code(L"R")->text(L" (red) channel of layer ")->code(L"n")
1202                 ->text(L", and hides the RGB channels of layer ")->code(L"n")
1203                 ->text(L". If keyer equals 1 then the specified layer will not be rendered, ")
1204                 ->text(L"instead it will be used as the key for the layer above.");
1205         sink.para()->text(L"Examples:");
1206         sink.example(L">> MIXER 1-0 KEYER 1");
1207         sink.example(
1208                 L">> MIXER 1-0 KEYER\n"
1209                 L"<< 201 MIXER OK\n"
1210                 L"<< 1", L"to retrieve the current state");
1211 }
1212
1213 std::wstring mixer_keyer_command(command_context& ctx)
1214 {
1215         if (ctx.parameters.empty())
1216                 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
1217
1218         transforms_applier transforms(ctx);
1219         bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1220         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1221         {
1222                 transform.image_transform.is_key = value;
1223                 return transform;
1224         }, 0, L"linear"));
1225         transforms.apply();
1226
1227         return L"202 MIXER OK\r\n";
1228 }
1229
1230 std::wstring ANIMATION_SYNTAX = L" {[duration:int] {[tween:string]|linear}|0 linear}}";
1231
1232 void mixer_chroma_describer(core::help_sink& sink, const core::help_repository& repo)
1233 {
1234         sink.short_description(L"Enable chroma keying on a layer.");
1235         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CHROMA {[color:none,green,blue] {[threshold:float] [softness:float] {[spill:float]}}" + ANIMATION_SYNTAX);
1236         sink.para()
1237                 ->text(L"Enables or disables chroma keying on the specified video layer. Giving no parameters returns the current chroma settings.");
1238         sink.para()->text(L"Examples:");
1239         sink.example(L">> MIXER 1-1 CHROMA green 0.10 0.20 25 easeinsine");
1240         sink.example(L">> MIXER 1-1 CHROMA none");
1241         sink.example(
1242                 L">> MIXER 1-1 BLEND\n"
1243                 L"<< 201 MIXER OK\n"
1244                 L"<< SCREEN", L"for getting the current blend mode");
1245 }
1246
1247 std::wstring mixer_chroma_command(command_context& ctx)
1248 {
1249         if (ctx.parameters.empty())
1250         {
1251                 auto chroma = get_current_transform(ctx).image_transform.chroma;
1252                 return L"201 MIXER OK\r\n"
1253                         + core::get_chroma_mode(chroma.key) + L" "
1254                         + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
1255                         + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
1256                         + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n";
1257         }
1258
1259         transforms_applier transforms(ctx);
1260         int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters.at(4)) : 0;
1261         std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters.at(5) : L"linear";
1262
1263         core::chroma chroma;
1264         chroma.key = get_chroma_mode(ctx.parameters.at(0));
1265
1266         if (chroma.key != core::chroma::type::none)
1267         {
1268                 chroma.threshold = boost::lexical_cast<double>(ctx.parameters.at(1));
1269                 chroma.softness = boost::lexical_cast<double>(ctx.parameters.at(2));
1270                 chroma.spill = ctx.parameters.size() > 3 ? boost::lexical_cast<double>(ctx.parameters.at(3)) : 0.0;
1271         }
1272
1273         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1274         {
1275                 transform.image_transform.chroma = chroma;
1276                 return transform;
1277         }, duration, tween));
1278         transforms.apply();
1279
1280         return L"202 MIXER OK\r\n";
1281 }
1282
1283 void mixer_blend_describer(core::help_sink& sink, const core::help_repository& repo)
1284 {
1285         sink.short_description(L"Set the blend mode for a layer.");
1286         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BLEND {[blend:string]|normal}");
1287         sink.para()
1288                 ->text(L"Sets the blend mode to use when compositing this layer with the background. ")
1289                 ->text(L"If no argument is given the current blend mode is returned.");
1290         sink.para()
1291                 ->text(L"Every layer can be set to use a different ")->url(L"http://en.wikipedia.org/wiki/Blend_modes", L"blend mode")
1292                 ->text(L" than the default ")->code(L"normal")->text(L" mode, similar to applications like Photoshop. ")
1293                 ->text(L"Some common uses are to use ")->code(L"screen")->text(L" to make all the black image data become transparent, ")
1294                 ->text(L"or to use ")->code(L"add")->text(L" to selectively lighten highlights.");
1295         sink.para()->text(L"Examples:");
1296         sink.example(L">> MIXER 1-1 BLEND OVERLAY");
1297         sink.example(
1298                 L">> MIXER 1-1 BLEND\n"
1299                 L"<< 201 MIXER OK\n"
1300                 L"<< SCREEN", L"for getting the current blend mode");
1301         sink.para()->text(L"See ")->see(L"Blend Modes")->text(L" for supported values for ")->code(L"blend")->text(L".");
1302 }
1303
1304 std::wstring mixer_blend_command(command_context& ctx)
1305 {
1306         if (ctx.parameters.empty())
1307                 return reply_value(ctx, [](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
1308
1309         transforms_applier transforms(ctx);
1310         auto value = get_blend_mode(ctx.parameters.at(0));
1311         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1312         {
1313                 transform.image_transform.blend_mode = value;
1314                 return transform;
1315         }, 0, L"linear"));
1316         transforms.apply();
1317
1318         return L"202 MIXER OK\r\n";
1319 }
1320
1321 template<typename Getter, typename Setter>
1322 std::wstring single_double_animatable_mixer_command(command_context& ctx, const Getter& getter, const Setter& setter)
1323 {
1324         if (ctx.parameters.empty())
1325                 return reply_value(ctx, getter);
1326
1327         transforms_applier transforms(ctx);
1328         double value = boost::lexical_cast<double>(ctx.parameters.at(0));
1329         int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1330         std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1331
1332         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1333         {
1334                 setter(transform, value);
1335                 return transform;
1336         }, duration, tween));
1337         transforms.apply();
1338
1339         return L"202 MIXER OK\r\n";
1340 }
1341
1342 void mixer_opacity_describer(core::help_sink& sink, const core::help_repository& repo)
1343 {
1344         sink.short_description(L"Change the opacity of a layer.");
1345         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} OPACITY {[opacity:float]" + ANIMATION_SYNTAX);
1346         sink.para()->text(L"Changes the opacity of the specified layer. The value is a float between 0 and 1.");
1347         sink.para()->text(L"Retrieves the opacity of the specified layer if no argument is given.");
1348         sink.para()->text(L"Examples:");
1349         sink.example(L">> MIXER 1-0 OPACITY 0.5 25 easeinsine");
1350         sink.example(
1351                 L">> MIXER 1-0 OPACITY\n"
1352                 L"<< 201 MIXER OK\n"
1353                 L"<< 0.5", L"to retrieve the current opacity");
1354 }
1355
1356 std::wstring mixer_opacity_command(command_context& ctx)
1357 {
1358         return single_double_animatable_mixer_command(
1359                         ctx,
1360                         [](const frame_transform& t) { return t.image_transform.opacity; },
1361                         [](frame_transform& t, double value) { t.image_transform.opacity = value; });
1362 }
1363
1364 void mixer_brightness_describer(core::help_sink& sink, const core::help_repository& repo)
1365 {
1366         sink.short_description(L"Change the brightness of a layer.");
1367         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} BRIGHTNESS {[brightness:float]" + ANIMATION_SYNTAX);
1368         sink.para()->text(L"Changes the brightness of the specified layer. The value is a float between 0 and 1.");
1369         sink.para()->text(L"Retrieves the brightness of the specified layer if no argument is given.");
1370         sink.para()->text(L"Examples:");
1371         sink.example(L">> MIXER 1-0 BRIGHTNESS 0.5 25 easeinsine");
1372         sink.example(
1373                 L">> MIXER 1-0 BRIGHTNESS\n"
1374                 L"<< 201 MIXER OK\n"
1375                 L"0.5", L"to retrieve the current brightness");
1376 }
1377
1378 std::wstring mixer_brightness_command(command_context& ctx)
1379 {
1380         return single_double_animatable_mixer_command(
1381                         ctx,
1382                         [](const frame_transform& t) { return t.image_transform.brightness; },
1383                         [](frame_transform& t, double value) { t.image_transform.brightness = value; });
1384 }
1385
1386 void mixer_saturation_describer(core::help_sink& sink, const core::help_repository& repo)
1387 {
1388         sink.short_description(L"Change the saturation of a layer.");
1389         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} SATURATION {[saturation:float]" + ANIMATION_SYNTAX);
1390         sink.para()->text(L"Changes the saturation of the specified layer. The value is a float between 0 and 1.");
1391         sink.para()->text(L"Retrieves the saturation of the specified layer if no argument is given.");
1392         sink.para()->text(L"Examples:");
1393         sink.example(L">> MIXER 1-0 SATURATION 0.5 25 easeinsine");
1394         sink.example(
1395                 L">> MIXER 1-0 SATURATION\n"
1396                 L"<< 201 MIXER OK\n"
1397                 L"<< 0.5", L"to retrieve the current saturation");
1398 }
1399
1400 std::wstring mixer_saturation_command(command_context& ctx)
1401 {
1402         return single_double_animatable_mixer_command(
1403                         ctx,
1404                         [](const frame_transform& t) { return t.image_transform.saturation; },
1405                         [](frame_transform& t, double value) { t.image_transform.saturation = value; });
1406 }
1407
1408 void mixer_contrast_describer(core::help_sink& sink, const core::help_repository& repo)
1409 {
1410         sink.short_description(L"Change the contrast of a layer.");
1411         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CONTRAST {[contrast:float]" + ANIMATION_SYNTAX);
1412         sink.para()->text(L"Changes the contrast of the specified layer. The value is a float between 0 and 1.");
1413         sink.para()->text(L"Retrieves the contrast of the specified layer if no argument is given.");
1414         sink.para()->text(L"Examples:");
1415         sink.example(L">> MIXER 1-0 CONTRAST 0.5 25 easeinsine");
1416         sink.example(
1417                 L">> MIXER 1-0 CONTRAST\n"
1418                 L"<< 201 MIXER OK\n"
1419                 L"<< 0.5", L"to retrieve the current contrast");
1420 }
1421
1422 std::wstring mixer_contrast_command(command_context& ctx)
1423 {
1424         return single_double_animatable_mixer_command(
1425                         ctx,
1426                         [](const frame_transform& t) { return t.image_transform.contrast; },
1427                         [](frame_transform& t, double value) { t.image_transform.contrast = value; });
1428 }
1429
1430 void mixer_levels_describer(core::help_sink& sink, const core::help_repository& repo)
1431 {
1432         sink.short_description(L"Adjust the video levels of a layer.");
1433         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);
1434         sink.para()
1435                 ->text(L"Adjusts the video levels of a layer. If no arguments are given the current levels are returned.");
1436         sink.definitions()
1437                 ->item(L"min-input, max-input", L"Defines the input range (between 0 and 1) to accept RGB values within.")
1438                 ->item(L"gamma", L"Adjusts the gamma of the image.")
1439                 ->item(L"min-output, max-output", L"Defines the output range (between 0 and 1) to scale the accepted input RGB values to.");
1440                 sink.para()->text(L"Examples:");
1441         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");
1442         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");
1443         sink.example(L">> MIXER 1-10 LEVELS 0 1 0.5 0 1 25 easeinsine", L"for adjusting the gamma to 0.5");
1444         sink.example(
1445                 L">> MIXER 1-10 LEVELS\n"
1446                 L"<< 201 MIXER OK\n"
1447                 L"<< 0 1 0.5 0 1", L"for retrieving the current levels");
1448 }
1449
1450 std::wstring mixer_levels_command(command_context& ctx)
1451 {
1452         if (ctx.parameters.empty())
1453         {
1454                 auto levels = get_current_transform(ctx).image_transform.levels;
1455                 return L"201 MIXER OK\r\n"
1456                         + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
1457                         + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
1458                         + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
1459                         + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
1460                         + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n";
1461         }
1462
1463         transforms_applier transforms(ctx);
1464         levels value;
1465         value.min_input = boost::lexical_cast<double>(ctx.parameters.at(0));
1466         value.max_input = boost::lexical_cast<double>(ctx.parameters.at(1));
1467         value.gamma = boost::lexical_cast<double>(ctx.parameters.at(2));
1468         value.min_output = boost::lexical_cast<double>(ctx.parameters.at(3));
1469         value.max_output = boost::lexical_cast<double>(ctx.parameters.at(4));
1470         int duration = ctx.parameters.size() > 5 ? boost::lexical_cast<int>(ctx.parameters[5]) : 0;
1471         std::wstring tween = ctx.parameters.size() > 6 ? ctx.parameters[6] : L"linear";
1472
1473         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1474         {
1475                 transform.image_transform.levels = value;
1476                 return transform;
1477         }, duration, tween));
1478         transforms.apply();
1479
1480         return L"202 MIXER OK\r\n";
1481 }
1482
1483 void mixer_fill_describer(core::help_sink& sink, const core::help_repository& repo)
1484 {
1485         sink.short_description(L"Change the fill position and scale of a layer.");
1486         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} FILL {[x:float] [y:float] [x-scale:float] [y-scale:float]" + ANIMATION_SYNTAX);
1487         sink.para()
1488                 ->text(L"Scales/positions the video stream on the specified layer. The concept is quite simple; ")
1489                 ->text(L"it comes from the ancient DVE machines like ADO. Imagine that the screen has a size of 1x1 ")
1490                 ->text(L"(not in pixel, but in an abstract measure). Then the coordinates of a full size picture is 0 0 1 1, ")
1491                 ->text(L"which means left edge is at coordinate 0, top edge at coordinate 0, width full size = 1, heigh full size = 1.");
1492         sink.para()
1493                 ->text(L"If you want to crop the picture on the left side (for wipe left to right) ")
1494                 ->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.");
1495         sink.para()->text(L"End coordinates of any wipe are allways the full picture 0 0 1 1.");
1496         sink.para()
1497                 ->text(L"With the FILL command it can make sense to have values between 1 and 0, ")
1498                 ->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, ")
1499                 ->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");
1500         sink.definitions()
1501                 ->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.")
1502                 ->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.")
1503                 ->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.")
1504                 ->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.");
1505         sink.para()->text(L"The positioning and scaling is done around the anchor point set by ")->see(L"MIXER ANCHOR")->text(L".");
1506         sink.para()->text(L"Examples:");
1507         sink.example(L">> MIXER 1-0 FILL 0.25 0.25 0.5 0.5 25 easeinsine");
1508         sink.example(
1509                 L">> MIXER 1-0 FILL\n"
1510                 L"<< 201 MIXER OK\n"
1511                 L"<< 0.25 0.25 0.5 0.5", L"gets the current fill");
1512 }
1513
1514 std::wstring mixer_fill_command(command_context& ctx)
1515 {
1516         if (ctx.parameters.empty())
1517         {
1518                 auto transform = get_current_transform(ctx).image_transform;
1519                 auto translation = transform.fill_translation;
1520                 auto scale = transform.fill_scale;
1521                 return L"201 MIXER OK\r\n"
1522                         + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1523                         + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1524                         + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1525                         + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1526         }
1527
1528         transforms_applier transforms(ctx);
1529         int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1530         std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1531         double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1532         double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1533         double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1534         double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1535
1536         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1537         {
1538                 transform.image_transform.fill_translation[0] = x;
1539                 transform.image_transform.fill_translation[1] = y;
1540                 transform.image_transform.fill_scale[0] = x_s;
1541                 transform.image_transform.fill_scale[1] = y_s;
1542                 return transform;
1543         }, duration, tween));
1544         transforms.apply();
1545
1546         return L"202 MIXER OK\r\n";
1547 }
1548
1549 void mixer_clip_describer(core::help_sink& sink, const core::help_repository& repo)
1550 {
1551         sink.short_description(L"Change the clipping viewport of a layer.");
1552         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} CLIP {[x:float] [y:float] [width:float] [height:float]" + ANIMATION_SYNTAX);
1553         sink.para()
1554                 ->text(L"Defines the rectangular viewport where a layer is rendered thru on the screen without being affected by ")
1555                 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1556                 ->text(L". See ")->see(L"MIXER CROP")->text(L" if you want to crop the layer before transforming it.");
1557         sink.definitions()
1558                 ->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.")
1559                 ->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.")
1560                 ->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.")
1561                 ->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.");
1562         sink.para()->text(L"Examples:");
1563         sink.example(L">> MIXER 1-0 CLIP 0.25 0.25 0.5 0.5 25 easeinsine");
1564         sink.example(
1565                 L">> MIXER 1-0 CLIP\n"
1566                 L"<< 201 MIXER OK\n"
1567                 L"<< 0.25 0.25 0.5 0.5", L"for retrieving the current clipping rect");
1568 }
1569
1570 std::wstring mixer_clip_command(command_context& ctx)
1571 {
1572         if (ctx.parameters.empty())
1573         {
1574                 auto transform = get_current_transform(ctx).image_transform;
1575                 auto translation = transform.clip_translation;
1576                 auto scale = transform.clip_scale;
1577
1578                 return L"201 MIXER OK\r\n"
1579                         + boost::lexical_cast<std::wstring>(translation[0]) + L" "
1580                         + boost::lexical_cast<std::wstring>(translation[1]) + L" "
1581                         + boost::lexical_cast<std::wstring>(scale[0]) + L" "
1582                         + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n";
1583         }
1584
1585         transforms_applier transforms(ctx);
1586         int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1587         std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1588         double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1589         double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1590         double x_s = boost::lexical_cast<double>(ctx.parameters.at(2));
1591         double y_s = boost::lexical_cast<double>(ctx.parameters.at(3));
1592
1593         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1594         {
1595                 transform.image_transform.clip_translation[0] = x;
1596                 transform.image_transform.clip_translation[1] = y;
1597                 transform.image_transform.clip_scale[0] = x_s;
1598                 transform.image_transform.clip_scale[1] = y_s;
1599                 return transform;
1600         }, duration, tween));
1601         transforms.apply();
1602
1603         return L"202 MIXER OK\r\n";
1604 }
1605
1606 void mixer_anchor_describer(core::help_sink& sink, const core::help_repository& repo)
1607 {
1608         sink.short_description(L"Change the anchor point of a layer.");
1609         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ANCHOR {[x:float] [y:float]" + ANIMATION_SYNTAX);
1610         sink.para()->text(L"Changes the anchor point of the specified layer, or returns the current values if no arguments are given.");
1611         sink.para()
1612                 ->text(L"The anchor point is around which ")->see(L"MIXER FILL")->text(L" and ")->see(L"MIXER ROTATION")
1613                 ->text(L" will be done from.");
1614         sink.definitions()
1615                 ->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.")
1616                 ->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.");
1617         sink.para()->text(L"Examples:");
1618         sink.example(L">> MIXER 1-10 ANCHOR 0.5 0.6 25 easeinsine", L"sets the anchor point");
1619         sink.example(
1620                 L">> MIXER 1-10 ANCHOR\n"
1621                 L"<< 201 MIXER OK\n"
1622                 L"<< 0.5 0.6", L"gets the anchor point");
1623 }
1624
1625 std::wstring mixer_anchor_command(command_context& ctx)
1626 {
1627         if (ctx.parameters.empty())
1628         {
1629                 auto transform = get_current_transform(ctx).image_transform;
1630                 auto anchor = transform.anchor;
1631                 return L"201 MIXER OK\r\n"
1632                         + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
1633                         + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n";
1634         }
1635
1636         transforms_applier transforms(ctx);
1637         int duration = ctx.parameters.size() > 2 ? boost::lexical_cast<int>(ctx.parameters[2]) : 0;
1638         std::wstring tween = ctx.parameters.size() > 3 ? ctx.parameters[3] : L"linear";
1639         double x = boost::lexical_cast<double>(ctx.parameters.at(0));
1640         double y = boost::lexical_cast<double>(ctx.parameters.at(1));
1641
1642         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) mutable -> frame_transform
1643         {
1644                 transform.image_transform.anchor[0] = x;
1645                 transform.image_transform.anchor[1] = y;
1646                 return transform;
1647         }, duration, tween));
1648         transforms.apply();
1649
1650         return L"202 MIXER OK\r\n";
1651 }
1652
1653 void mixer_crop_describer(core::help_sink& sink, const core::help_repository& repo)
1654 {
1655         sink.short_description(L"Crop a layer.");
1656         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);
1657         sink.para()
1658                 ->text(L"Defines how a layer should be cropped before making other transforms via ")
1659                 ->see(L"MIXER FILL")->text(L", ")->see(L"MIXER ROTATION")->text(L" and ")->see(L"MIXER PERSPECTIVE")
1660                 ->text(L". See ")->see(L"MIXER CLIP")->text(L" if you want to change the viewport relative to the screen instead.");
1661         sink.definitions()
1662                 ->item(L"left-edge", L"A value between 0 and 1 defining how far into the layer to crop from the left edge.")
1663                 ->item(L"top-edge", L"A value between 0 and 1 defining how far into the layer to crop from the top edge.")
1664                 ->item(L"right-edge", L"A value between 1 and 0 defining how far into the layer to crop from the right edge.")
1665                 ->item(L"bottom-edge", L"A value between 1 and 0 defining how far into the layer to crop from the bottom edge.");
1666         sink.para()->text(L"Examples:");
1667         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");
1668         sink.example(
1669                 L">> MIXER 1-0 CROP\n"
1670                 L"<< 201 MIXER OK\n"
1671                 L"<< 0.25 0.25 0.75 0.75", L"for retrieving the current crop edges");
1672 }
1673
1674 std::wstring mixer_crop_command(command_context& ctx)
1675 {
1676         if (ctx.parameters.empty())
1677         {
1678                 auto crop = get_current_transform(ctx).image_transform.crop;
1679                 return L"201 MIXER OK\r\n"
1680                         + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
1681                         + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
1682                         + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
1683                         + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n";
1684         }
1685
1686         transforms_applier transforms(ctx);
1687         int duration = ctx.parameters.size() > 4 ? boost::lexical_cast<int>(ctx.parameters[4]) : 0;
1688         std::wstring tween = ctx.parameters.size() > 5 ? ctx.parameters[5] : L"linear";
1689         double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1690         double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1691         double lr_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1692         double lr_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1693
1694         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1695         {
1696                 transform.image_transform.crop.ul[0] = ul_x;
1697                 transform.image_transform.crop.ul[1] = ul_y;
1698                 transform.image_transform.crop.lr[0] = lr_x;
1699                 transform.image_transform.crop.lr[1] = lr_y;
1700                 return transform;
1701         }, duration, tween));
1702         transforms.apply();
1703
1704         return L"202 MIXER OK\r\n";
1705 }
1706
1707 void mixer_rotation_describer(core::help_sink& sink, const core::help_repository& repo)
1708 {
1709         sink.short_description(L"Rotate a layer.");
1710         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} ROTATION {[angle:float]" + ANIMATION_SYNTAX);
1711         sink.para()
1712                 ->text(L"Returns or modifies the angle of which a layer is rotated by (clockwise degrees) ")
1713                 ->text(L"around the point specified by ")->see(L"MIXER ANCHOR")->text(L".");
1714         sink.para()->text(L"Examples:");
1715         sink.example(L">> MIXER 1-0 ROTATION 45 25 easeinsine");
1716         sink.example(
1717                 L">> MIXER 1-0 ROTATION\n"
1718                 L"<< 201 MIXER OK\n"
1719                 L"<< 45", L"to retrieve the current angle");
1720 }
1721
1722 std::wstring mixer_rotation_command(command_context& ctx)
1723 {
1724         static const double PI = 3.141592653589793;
1725
1726         return single_double_animatable_mixer_command(
1727                         ctx,
1728                         [](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; },
1729                         [](frame_transform& t, double value) { t.image_transform.angle = value * PI / 180.0; });
1730 }
1731
1732 void mixer_perspective_describer(core::help_sink& sink, const core::help_repository& repo)
1733 {
1734         sink.short_description(L"Adjust the perspective transform of a layer.");
1735         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);
1736         sink.para()
1737                 ->text(L"Perspective transforms (corner pins or distorts if you will) a layer.");
1738         sink.definitions()
1739                 ->item(L"top-left-x, top-left-y", L"Defines the x:y coordinate of the top left corner.")
1740                 ->item(L"top-right-x, top-right-y", L"Defines the x:y coordinate of the top right corner.")
1741                 ->item(L"bottom-right-x, bottom-right-y", L"Defines the x:y coordinate of the bottom right corner.")
1742                 ->item(L"bottom-left-x, bottom-left-y", L"Defines the x:y coordinate of the bottom left corner.");
1743         sink.para()->text(L"Examples:");
1744         sink.example(L">> MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1 25 easeinsine");
1745         sink.example(
1746                 L">> MIXER 1-10 PERSPECTIVE\n"
1747                 L"<< 201 MIXER OK\n"
1748                 L"<< 0.4 0.4 0.6 0.4 1 1 0 1", L"for retrieving the current corners");
1749 }
1750
1751 std::wstring mixer_perspective_command(command_context& ctx)
1752 {
1753         if (ctx.parameters.empty())
1754         {
1755                 auto perspective = get_current_transform(ctx).image_transform.perspective;
1756                 return
1757                         L"201 MIXER OK\r\n"
1758                         + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
1759                         + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
1760                         + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
1761                         + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
1762                         + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
1763                         + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
1764                         + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
1765                         + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n";
1766         }
1767
1768         transforms_applier transforms(ctx);
1769         int duration = ctx.parameters.size() > 8 ? boost::lexical_cast<int>(ctx.parameters[8]) : 0;
1770         std::wstring tween = ctx.parameters.size() > 9 ? ctx.parameters[9] : L"linear";
1771         double ul_x = boost::lexical_cast<double>(ctx.parameters.at(0));
1772         double ul_y = boost::lexical_cast<double>(ctx.parameters.at(1));
1773         double ur_x = boost::lexical_cast<double>(ctx.parameters.at(2));
1774         double ur_y = boost::lexical_cast<double>(ctx.parameters.at(3));
1775         double lr_x = boost::lexical_cast<double>(ctx.parameters.at(4));
1776         double lr_y = boost::lexical_cast<double>(ctx.parameters.at(5));
1777         double ll_x = boost::lexical_cast<double>(ctx.parameters.at(6));
1778         double ll_y = boost::lexical_cast<double>(ctx.parameters.at(7));
1779
1780         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1781         {
1782                 transform.image_transform.perspective.ul[0] = ul_x;
1783                 transform.image_transform.perspective.ul[1] = ul_y;
1784                 transform.image_transform.perspective.ur[0] = ur_x;
1785                 transform.image_transform.perspective.ur[1] = ur_y;
1786                 transform.image_transform.perspective.lr[0] = lr_x;
1787                 transform.image_transform.perspective.lr[1] = lr_y;
1788                 transform.image_transform.perspective.ll[0] = ll_x;
1789                 transform.image_transform.perspective.ll[1] = ll_y;
1790                 return transform;
1791         }, duration, tween));
1792         transforms.apply();
1793
1794         return L"202 MIXER OK\r\n";
1795 }
1796
1797 void mixer_mipmap_describer(core::help_sink& sink, const core::help_repository& repo)
1798 {
1799         sink.short_description(L"Enable or disable mipmapping for a layer.");
1800         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} MIPMAP {[mipmap:0,1]|0}");
1801         sink.para()
1802                 ->text(L"Sets whether to use mipmapping (anisotropic filtering if supported) on a layer or not. ")
1803                 ->text(L"If no argument is given the current state is returned.");
1804         sink.para()->text(L"Mipmapping reduces aliasing when downscaling/perspective transforming.");
1805         sink.para()->text(L"Examples:");
1806         sink.example(L">> MIXER 1-10 MIPMAP 1", L"for turning mipmapping on");
1807         sink.example(
1808                 L">> MIXER 1-10 MIPMAP\n"
1809                 L"<< 201 MIXER OK\n"
1810                 L"<< 1", L"for getting the current state");
1811 }
1812
1813 std::wstring mixer_mipmap_command(command_context& ctx)
1814 {
1815         if (ctx.parameters.empty())
1816                 return reply_value(ctx, [](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
1817
1818         transforms_applier transforms(ctx);
1819         bool value = boost::lexical_cast<int>(ctx.parameters.at(0));
1820         transforms.add(stage::transform_tuple_t(ctx.layer_index(), [=](frame_transform transform) -> frame_transform
1821         {
1822                 transform.image_transform.use_mipmap = value;
1823                 return transform;
1824         }, 0, L"linear"));
1825         transforms.apply();
1826
1827         return L"202 MIXER OK\r\n";
1828 }
1829
1830 void mixer_volume_describer(core::help_sink& sink, const core::help_repository& repo)
1831 {
1832         sink.short_description(L"Change the volume of a layer.");
1833         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]|-0} VOLUME {[volume:float]" + ANIMATION_SYNTAX);
1834         sink.para()->text(L"Changes the volume of the specified layer. The 1.0 is the original volume, which can be attenuated or amplified.");
1835         sink.para()->text(L"Retrieves the volume of the specified layer if no argument is given.");
1836         sink.para()->text(L"Examples:");
1837         sink.example(L">> MIXER 1-0 VOLUME 0 25 linear", L"for fading out the audio during 25 frames");
1838         sink.example(L">> MIXER 1-0 VOLUME 1.5", L"for amplifying the audio by 50%");
1839         sink.example(
1840                 L">> MIXER 1-0 VOLUME\n"
1841                 L"<< 201 MIXER OK\n"
1842                 L"<< 0.8", L"to retrieve the current volume");
1843 }
1844
1845 std::wstring mixer_volume_command(command_context& ctx)
1846 {
1847         return single_double_animatable_mixer_command(
1848                 ctx,
1849                 [](const frame_transform& t) { return t.audio_transform.volume; },
1850                 [](frame_transform& t, double value) { t.audio_transform.volume = value; });
1851 }
1852
1853 void mixer_mastervolume_describer(core::help_sink& sink, const core::help_repository& repo)
1854 {
1855         sink.short_description(L"Change the volume of an entire channel.");
1856         sink.syntax(L"MIXER [video_channel:int] MASTERVOLUME {[volume:float]}");
1857         sink.para()->text(L"Changes or retrieves (giving no argument) the volume of the entire channel.");
1858         sink.para()->text(L"Examples:");
1859         sink.example(L">> MIXER 1 MASTERVOLUME 0");
1860         sink.example(L">> MIXER 1 MASTERVOLUME 1");
1861         sink.example(L">> MIXER 1 MASTERVOLUME 0.5");
1862 }
1863
1864 std::wstring mixer_mastervolume_command(command_context& ctx)
1865 {
1866         if (ctx.parameters.empty())
1867         {
1868                 auto volume = ctx.channel.channel->mixer().get_master_volume();
1869                 return L"201 MIXER OK\r\n" + boost::lexical_cast<std::wstring>(volume)+L"\r\n";
1870         }
1871
1872         float master_volume = boost::lexical_cast<float>(ctx.parameters.at(0));
1873         ctx.channel.channel->mixer().set_master_volume(master_volume);
1874
1875         return L"202 MIXER OK\r\n";
1876 }
1877
1878 void mixer_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1879 {
1880         sink.short_description(L"Create a grid of video layers.");
1881         sink.syntax(L"MIXER [video_channel:int] GRID [resolution:int]" + ANIMATION_SYNTAX);
1882         sink.para()
1883                 ->text(L"Creates a grid of video layer in ascending order of the layer index, i.e. if ")
1884                 ->code(L"resolution")->text(L" equals 2 then a 2x2 grid of layers will be created starting from layer 1.");
1885         sink.para()->text(L"Examples:");
1886         sink.example(L">> MIXER 1 GRID 2");
1887 }
1888
1889 std::wstring mixer_grid_command(command_context& ctx)
1890 {
1891         transforms_applier transforms(ctx);
1892         int duration = ctx.parameters.size() > 1 ? boost::lexical_cast<int>(ctx.parameters[1]) : 0;
1893         std::wstring tween = ctx.parameters.size() > 2 ? ctx.parameters[2] : L"linear";
1894         int n = boost::lexical_cast<int>(ctx.parameters.at(0));
1895         double delta = 1.0 / static_cast<double>(n);
1896         for (int x = 0; x < n; ++x)
1897         {
1898                 for (int y = 0; y < n; ++y)
1899                 {
1900                         int index = x + y*n + 1;
1901                         transforms.add(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
1902                         {
1903                                 transform.image_transform.fill_translation[0] = x*delta;
1904                                 transform.image_transform.fill_translation[1] = y*delta;
1905                                 transform.image_transform.fill_scale[0] = delta;
1906                                 transform.image_transform.fill_scale[1] = delta;
1907                                 transform.image_transform.clip_translation[0] = x*delta;
1908                                 transform.image_transform.clip_translation[1] = y*delta;
1909                                 transform.image_transform.clip_scale[0] = delta;
1910                                 transform.image_transform.clip_scale[1] = delta;
1911                                 return transform;
1912                         }, duration, tween));
1913                 }
1914         }
1915         transforms.apply();
1916
1917         return L"202 MIXER OK\r\n";
1918 }
1919
1920 void mixer_commit_describer(core::help_sink& sink, const core::help_repository& repo)
1921 {
1922         sink.short_description(L"Commit all deferred mixer transforms.");
1923         sink.syntax(L"MIXER [video_channel:int] COMMIT");
1924         sink.para()->text(L"Commits all deferred mixer transforms on the specified channel. This ensures that all animations start at the same exact frame.");
1925         sink.para()->text(L"Examples:");
1926         sink.example(
1927                 L">> MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\n"
1928                 L">> MIXER 1-2 FILL 0.5 0 0.5 0.5 25 DEFER\n"
1929                 L">> MIXER 1 COMMIT");
1930 }
1931
1932 std::wstring mixer_commit_command(command_context& ctx)
1933 {
1934         transforms_applier transforms(ctx);
1935         transforms.commit_deferred();
1936
1937         return L"202 MIXER OK\r\n";
1938 }
1939
1940 void mixer_clear_describer(core::help_sink& sink, const core::help_repository& repo)
1941 {
1942         sink.short_description(L"Clear all transformations on a channel or layer.");
1943         sink.syntax(L"MIXER [video_channel:int]{-[layer:int]} CLEAR");
1944         sink.para()->text(L"Clears all transformations on a channel or layer.");
1945         sink.para()->text(L"Examples:");
1946         sink.example(L">> MIXER 1 CLEAR", L"for clearing transforms on entire channel 1");
1947         sink.example(L">> MIXER 1-1 CLEAR", L"for clearing transforms on layer 1-1");
1948 }
1949
1950 std::wstring mixer_clear_command(command_context& ctx)
1951 {
1952         int layer = ctx.layer_id;
1953
1954         if (layer == -1)
1955                 ctx.channel.channel->stage().clear_transforms();
1956         else
1957                 ctx.channel.channel->stage().clear_transforms(layer);
1958
1959         return L"202 MIXER OK\r\n";
1960 }
1961
1962 void channel_grid_describer(core::help_sink& sink, const core::help_repository& repo)
1963 {
1964         sink.short_description(L"Open a new channel displaying a grid with the contents of all the existing channels.");
1965         sink.syntax(L"CHANNEL_GRID");
1966         sink.para()->text(L"Opens a new channel and displays a grid with the contents of all the existing channels.");
1967         sink.para()
1968                 ->text(L"The element ")->code(L"<channel-grid>true</channel-grid>")->text(L" must be present in ")
1969                 ->code(L"casparcg.config")->text(L" for this to work correctly.");
1970 }
1971
1972 std::wstring channel_grid_command(command_context& ctx)
1973 {
1974         int index = 1;
1975         auto self = ctx.channels.back();
1976
1977         core::diagnostics::scoped_call_context save;
1978         core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
1979
1980         std::vector<std::wstring> params;
1981         params.push_back(L"SCREEN");
1982         params.push_back(L"0");
1983         params.push_back(L"NAME");
1984         params.push_back(L"Channel Grid Window");
1985         auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage());
1986
1987         self.channel->output().add(screen);
1988
1989         for (auto& channel : ctx.channels)
1990         {
1991                 if (channel.channel != self.channel)
1992                 {
1993                         core::diagnostics::call_context::for_thread().layer = index;
1994                         auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(channel.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
1995                         self.channel->stage().load(index, producer, false);
1996                         self.channel->stage().play(index);
1997                         index++;
1998                 }
1999         }
2000
2001         auto num_channels = ctx.channels.size() - 1;
2002         int square_side_length = std::ceil(std::sqrt(num_channels));
2003
2004         ctx.channel_index = self.channel->index();
2005         ctx.channel = self;
2006         ctx.parameters.clear();
2007         ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
2008         mixer_grid_command(ctx);
2009
2010         return L"202 CHANNEL_GRID OK\r\n";
2011 }
2012
2013 // Thumbnail Commands
2014
2015 void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
2016 {
2017         sink.short_description(L"List all thumbnails.");
2018         sink.syntax(L"THUMBNAIL LIST");
2019         sink.para()->text(L"Lists all thumbnails.");
2020         sink.para()->text(L"Examples:");
2021         sink.example(
2022                 L">> THUMBNAIL LIST\n"
2023                 L"<< 200 THUMBNAIL LIST OK\n"
2024                 L"<< \"AMB\" 20130301T124409 1149\n"
2025                 L"<< \"foo/bar\" 20130523T234001 244");
2026 }
2027
2028 std::wstring thumbnail_list_command(command_context& ctx)
2029 {
2030         std::wstringstream replyString;
2031         replyString << L"200 THUMBNAIL LIST OK\r\n";
2032
2033         for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2034         {
2035                 if (boost::filesystem::is_regular_file(itr->path()))
2036                 {
2037                         if (!boost::iequals(itr->path().extension().wstring(), L".png"))
2038                                 continue;
2039
2040                         auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size() - 1, itr->path().wstring().size()));
2041
2042                         auto str = relativePath.replace_extension(L"").generic_wstring();
2043                         if (str[0] == '\\' || str[0] == '/')
2044                                 str = std::wstring(str.begin() + 1, str.end());
2045
2046                         auto mtime = boost::filesystem::last_write_time(itr->path());
2047                         auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2048                         auto file_size = boost::filesystem::file_size(itr->path());
2049
2050                         replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2051                 }
2052         }
2053
2054         replyString << L"\r\n";
2055
2056         return boost::to_upper_copy(replyString.str());
2057 }
2058
2059 void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
2060 {
2061         sink.short_description(L"Retrieve a thumbnail.");
2062         sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
2063         sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
2064         sink.para()->text(L"Examples:");
2065         sink.example(
2066                 L">> THUMBNAIL RETRIEVE foo/bar\n"
2067                 L"<< 201 THUMBNAIL RETRIEVE OK\n"
2068                 L"<< ...base64 data...");
2069 }
2070
2071 std::wstring thumbnail_retrieve_command(command_context& ctx)
2072 {
2073         std::wstring filename = env::thumbnails_folder();
2074         filename.append(ctx.parameters.at(0));
2075         filename.append(L".png");
2076
2077         std::wstring file_contents;
2078
2079         auto found_file = find_case_insensitive(filename);
2080
2081         if (found_file)
2082                 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2083
2084         if (file_contents.empty())
2085                 CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
2086
2087         std::wstringstream reply;
2088
2089         reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2090         reply << file_contents;
2091         reply << L"\r\n";
2092         return reply.str();
2093 }
2094
2095 void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
2096 {
2097         sink.short_description(L"Regenerate a thumbnail.");
2098         sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
2099         sink.para()->text(L"Regenerates a thumbnail.");
2100 }
2101
2102 std::wstring thumbnail_generate_command(command_context& ctx)
2103 {
2104         if (ctx.thumb_gen)
2105         {
2106                 ctx.thumb_gen->generate(ctx.parameters.at(0));
2107                 return L"202 THUMBNAIL GENERATE OK\r\n";
2108         }
2109         else
2110                 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2111 }
2112
2113 void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
2114 {
2115         sink.short_description(L"Regenerate all thumbnails.");
2116         sink.syntax(L"THUMBNAIL GENERATE_ALL");
2117         sink.para()->text(L"Regenerates all thumbnails.");
2118 }
2119
2120 std::wstring thumbnail_generateall_command(command_context& ctx)
2121 {
2122         if (ctx.thumb_gen)
2123         {
2124                 ctx.thumb_gen->generate_all();
2125                 return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
2126         }
2127         else
2128                 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Thumbnail generation turned off"));
2129 }
2130
2131 // Query Commands
2132
2133 void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
2134 {
2135         sink.short_description(L"Get information about a media file.");
2136         sink.syntax(L"CINF [filename:string]");
2137         sink.para()->text(L"Returns information about a media file.");
2138 }
2139
2140 std::wstring cinf_command(command_context& ctx)
2141 {
2142         std::wstring info;
2143         for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
2144         {
2145                 auto path = itr->path();
2146                 auto file = path.replace_extension(L"").filename().wstring();
2147                 if (boost::iequals(file, ctx.parameters.at(0)))
2148                         info += MediaInfo(itr->path(), ctx.media_info_repo);
2149         }
2150
2151         if (info.empty())
2152                 CASPAR_THROW_EXCEPTION(file_not_found());
2153
2154         std::wstringstream replyString;
2155         replyString << L"200 CINF OK\r\n";
2156         replyString << info << "\r\n";
2157
2158         return replyString.str();
2159 }
2160
2161 void cls_describer(core::help_sink& sink, const core::help_repository& repo)
2162 {
2163         sink.short_description(L"List all media files.");
2164         sink.syntax(L"CLS");
2165         sink.para()
2166                 ->text(L"Lists all media files in the ")->code(L"media")->text(L" folder. Use the command ")
2167                 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
2168 }
2169
2170 std::wstring cls_command(command_context& ctx)
2171 {
2172         std::wstringstream replyString;
2173         replyString << L"200 CLS OK\r\n";
2174         replyString << ListMedia(ctx.media_info_repo);
2175         replyString << L"\r\n";
2176         return boost::to_upper_copy(replyString.str());
2177 }
2178
2179 void tls_describer(core::help_sink& sink, const core::help_repository& repo)
2180 {
2181         sink.short_description(L"List all templates.");
2182         sink.syntax(L"TLS");
2183         sink.para()
2184                 ->text(L"Lists all template files in the ")->code(L"templates")->text(L" folder. Use the command ")
2185                 ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
2186 }
2187
2188 std::wstring tls_command(command_context& ctx)
2189 {
2190         std::wstringstream replyString;
2191         replyString << L"200 TLS OK\r\n";
2192
2193         replyString << ListTemplates(ctx.cg_registry);
2194         replyString << L"\r\n";
2195
2196         return replyString.str();
2197 }
2198
2199 void version_describer(core::help_sink& sink, const core::help_repository& repo)
2200 {
2201         sink.short_description(L"Get version information.");
2202         sink.syntax(L"VERSION {[component:string]}");
2203         sink.para()->text(L"Returns the version of specified component.");
2204         sink.para()->text(L"Examples:");
2205         sink.example(
2206                 L">> VERSION\n"
2207                 L"<< 201 VERSION OK\n"
2208                 L"<< 2.1.0.f207a33 STABLE");
2209         sink.example(
2210                 L">> VERSION SERVER\n"
2211                 L"<< 201 VERSION OK\n"
2212                 L"<< 2.1.0.f207a33 STABLE");
2213         sink.example(
2214                 L">> VERSION FLASH\n"
2215                 L"<< 201 VERSION OK\n"
2216                 L"<< 11.8.800.94");
2217 }
2218
2219 std::wstring version_command(command_context& ctx)
2220 {
2221         if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
2222         {
2223                 auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
2224
2225                 return L"201 VERSION OK\r\n" + version + L"\r\n";
2226         }
2227
2228         return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
2229 }
2230
2231 void info_describer(core::help_sink& sink, const core::help_repository& repo)
2232 {
2233         sink.short_description(L"Get a list of the available channels.");
2234         sink.syntax(L"INFO");
2235         sink.para()->text(L"Retrieves a list of the available channels.");
2236         sink.example(
2237                 L">> INFO\n"
2238                 L"<< 200 INFO OK\n"
2239                 L"<< 1 720p5000 PLAYING\n"
2240                 L"<< 2 PAL PLAYING");
2241 }
2242
2243 std::wstring info_command(command_context& ctx)
2244 {
2245         std::wstringstream replyString;
2246         // This is needed for backwards compatibility with old clients
2247         replyString << L"200 INFO OK\r\n";
2248         for (size_t n = 0; n < ctx.channels.size(); ++n)
2249                 replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
2250         replyString << L"\r\n";
2251         return replyString.str();
2252 }
2253
2254 std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
2255 {
2256         std::wstringstream replyString;
2257
2258         if (command.empty())
2259                 replyString << L"201 INFO OK\r\n";
2260         else
2261                 replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
2262
2263         boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
2264         boost::property_tree::xml_parser::write_xml(replyString, info, w);
2265         replyString << L"\r\n";
2266         return replyString.str();
2267 }
2268
2269 void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
2270 {
2271         sink.short_description(L"Get information about a channel or a layer.");
2272         sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
2273         sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
2274         sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
2275 }
2276
2277 std::wstring info_channel_command(command_context& ctx)
2278 {
2279         boost::property_tree::wptree info;
2280         int layer = ctx.layer_index(std::numeric_limits<int>::min());
2281
2282         if (layer == std::numeric_limits<int>::min())
2283         {
2284                 info.add_child(L"channel", ctx.channel.channel->info())
2285                         .add(L"index", ctx.channel_index);
2286         }
2287         else
2288         {
2289                 if (ctx.parameters.size() >= 1)
2290                 {
2291                         if (boost::iequals(ctx.parameters.at(0), L"B"))
2292                                 info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
2293                         else
2294                                 info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
2295                 }
2296                 else
2297                 {
2298                         info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
2299                 }
2300         }
2301
2302         return create_info_xml_reply(info);
2303 }
2304
2305 void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
2306 {
2307         sink.short_description(L"Get information about a template.");
2308         sink.syntax(L"INFO TEMPLATE [template:string]");
2309         sink.para()->text(L"Gets information about the specified template.");
2310 }
2311
2312 std::wstring info_template_command(command_context& ctx)
2313 {
2314         auto filename = ctx.parameters.at(0);
2315
2316         std::wstringstream str;
2317         str << u16(ctx.cg_registry->read_meta_info(filename));
2318         boost::property_tree::wptree info;
2319         boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
2320
2321         return create_info_xml_reply(info, L"TEMPLATE");
2322 }
2323
2324 void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
2325 {
2326         sink.short_description(L"Get the contents of the configuration used.");
2327         sink.syntax(L"INFO CONFIG");
2328         sink.para()->text(L"Gets the contents of the configuration used.");
2329 }
2330
2331 std::wstring info_config_command(command_context& ctx)
2332 {
2333         return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
2334 }
2335
2336 void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
2337 {
2338         sink.short_description(L"Get information about the paths used.");
2339         sink.syntax(L"INFO PATHS");
2340         sink.para()->text(L"Gets information about the paths used.");
2341 }
2342
2343 std::wstring info_paths_command(command_context& ctx)
2344 {
2345         boost::property_tree::wptree info;
2346         info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
2347         info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
2348
2349         return create_info_xml_reply(info, L"PATHS");
2350 }
2351
2352 void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
2353 {
2354         sink.short_description(L"Get system information.");
2355         sink.syntax(L"INFO SYSTEM");
2356         sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
2357 }
2358
2359 std::wstring info_system_command(command_context& ctx)
2360 {
2361         boost::property_tree::wptree info;
2362
2363         info.add(L"system.name", caspar::system_product_name());
2364         info.add(L"system.os.description", caspar::os_description());
2365         info.add(L"system.cpu", caspar::cpu_info());
2366
2367         ctx.system_info_repo->fill_information(info);
2368
2369         return create_info_xml_reply(info, L"SYSTEM");
2370 }
2371
2372 void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
2373 {
2374         sink.short_description(L"Get detailed information about all channels.");
2375         sink.syntax(L"INFO SERVER");
2376         sink.para()->text(L"Gets detailed information about all channels.");
2377 }
2378
2379 std::wstring info_server_command(command_context& ctx)
2380 {
2381         boost::property_tree::wptree info;
2382
2383         int index = 0;
2384         for (auto& channel : ctx.channels)
2385                 info.add_child(L"channels.channel", channel.channel->info())
2386                                 .add(L"index", ++index);
2387
2388         return create_info_xml_reply(info, L"SERVER");
2389 }
2390
2391 void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
2392 {
2393         sink.short_description(L"Get detailed information about all AMCP Command Queues.");
2394         sink.syntax(L"INFO QUEUES");
2395         sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
2396 }
2397
2398 std::wstring info_queues_command(command_context& ctx)
2399 {
2400         return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
2401 }
2402
2403 void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
2404 {
2405         sink.short_description(L"Lists all known threads in the server.");
2406         sink.syntax(L"INFO THREADS");
2407         sink.para()->text(L"Lists all known threads in the server.");
2408 }
2409
2410 std::wstring info_threads_command(command_context& ctx)
2411 {
2412         std::wstringstream replyString;
2413         replyString << L"200 INFO THREADS OK\r\n";
2414
2415         for (auto& thread : get_thread_infos())
2416         {
2417                 replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
2418         }
2419
2420         replyString << L"\r\n";
2421         return replyString.str();
2422 }
2423
2424 void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
2425 {
2426         sink.short_description(L"Get the current delay on a channel or a layer.");
2427         sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
2428         sink.para()->text(L"Get the current delay on the specified channel or layer.");
2429 }
2430
2431 std::wstring info_delay_command(command_context& ctx)
2432 {
2433         boost::property_tree::wptree info;
2434         auto layer = ctx.layer_index(std::numeric_limits<int>::min());
2435
2436         if (layer == std::numeric_limits<int>::min())
2437                 info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
2438         else
2439                 info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
2440                         .add(L"index", layer);
2441
2442         return create_info_xml_reply(info, L"DELAY");
2443 }
2444
2445 void diag_describer(core::help_sink& sink, const core::help_repository& repo)
2446 {
2447         sink.short_description(L"Open the diagnostics window.");
2448         sink.syntax(L"DIAG");
2449         sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
2450 }
2451
2452 std::wstring diag_command(command_context& ctx)
2453 {
2454         core::diagnostics::osd::show_graphs(true);
2455
2456         return L"202 DIAG OK\r\n";
2457 }
2458
2459 static const int WIDTH = 80;
2460
2461 struct max_width_sink : public core::help_sink
2462 {
2463         std::size_t max_width = 0;
2464
2465         void begin_item(const std::wstring& name) override
2466         {
2467                 max_width = std::max(name.length(), max_width);
2468         };
2469 };
2470
2471 struct short_description_sink : public core::help_sink
2472 {
2473         std::size_t width;
2474         std::wstringstream& out;
2475
2476         short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
2477
2478         void begin_item(const std::wstring& name) override
2479         {
2480                 out << std::left << std::setw(width + 1) << name;
2481         };
2482
2483         void short_description(const std::wstring& short_description) override
2484         {
2485                 out << short_description << L"\r\n";
2486         };
2487 };
2488
2489 struct simple_paragraph_builder : core::paragraph_builder
2490 {
2491         std::wostringstream out;
2492         std::wstringstream& commit_to;
2493
2494         simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
2495         ~simple_paragraph_builder()
2496         {
2497                 commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
2498         }
2499         spl::shared_ptr<paragraph_builder> text(std::wstring text) override
2500         {
2501                 out << std::move(text);
2502                 return shared_from_this();
2503         }
2504         spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
2505         spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
2506         spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name)  override { return text(std::move(url)); }
2507 };
2508
2509 struct simple_definition_list_builder : core::definition_list_builder
2510 {
2511         std::wstringstream& out;
2512
2513         simple_definition_list_builder(std::wstringstream& out) : out(out) { }
2514         ~simple_definition_list_builder()
2515         {
2516                 out << L"\n";
2517         }
2518
2519         spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
2520         {
2521                 out << core::indent(core::wordwrap(term, WIDTH - 2), L"  ");
2522                 out << core::indent(core::wordwrap(description, WIDTH - 4), L"    ");
2523                 return shared_from_this();
2524         }
2525 };
2526
2527 struct long_description_sink : public core::help_sink
2528 {
2529         std::wstringstream& out;
2530
2531         long_description_sink(std::wstringstream& out) : out(out) { }
2532
2533         void syntax(const std::wstring& syntax) override
2534         {
2535                 out << L"Syntax:\n";
2536                 out << core::indent(core::wordwrap(syntax, WIDTH - 2), L"  ") << L"\n";
2537         };
2538
2539         spl::shared_ptr<core::paragraph_builder> para() override
2540         {
2541                 return spl::make_shared<simple_paragraph_builder>(out);
2542         }
2543
2544         spl::shared_ptr<core::definition_list_builder> definitions() override
2545         {
2546                 return spl::make_shared<simple_definition_list_builder>(out);
2547         }
2548
2549         void example(const std::wstring& code, const std::wstring& caption) override
2550         {
2551                 out << core::indent(core::wordwrap(code, WIDTH - 2), L"  ");
2552
2553                 if (!caption.empty())
2554                         out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L"  ");
2555
2556                 out << L"\n";
2557         }
2558 private:
2559         void begin_item(const std::wstring& name) override
2560         {
2561                 out << name << L"\n\n";
2562         };
2563 };
2564
2565 std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2566 {
2567         std::wstringstream result;
2568         result << L"200 " << help_command << L" OK\r\n";
2569         max_width_sink width;
2570         ctx.help_repo->help(tags, width);
2571         short_description_sink sink(width.max_width, result);
2572         sink.width = width.max_width;
2573         ctx.help_repo->help(tags, sink);
2574         result << L"\r\n";
2575         return result.str();
2576 }
2577
2578 std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
2579 {
2580         std::wstringstream result;
2581         result << L"201 " << help_command << L" OK\r\n";
2582         auto joined = boost::join(ctx.parameters, L" ");
2583         long_description_sink sink(result);
2584         ctx.help_repo->help(tags, joined, sink);
2585         result << L"\r\n";
2586         return result.str();
2587 }
2588
2589 void help_describer(core::help_sink& sink, const core::help_repository& repository)
2590 {
2591         sink.short_description(L"Show online help for AMCP commands.");
2592         sink.syntax(LR"(HELP {[command:string]})");
2593         sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
2594         sink.example(L">> HELP", L"Shows a list of commands.");
2595         sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
2596 }
2597
2598 std::wstring help_command(command_context& ctx)
2599 {
2600         if (ctx.parameters.size() == 0)
2601                 return create_help_list(L"HELP", ctx, { L"AMCP" });
2602         else
2603                 return create_help_details(L"HELP", ctx, { L"AMCP" });
2604 }
2605
2606 void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
2607 {
2608         sink.short_description(L"Show online help for producers.");
2609         sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
2610         sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
2611         sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
2612         sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
2613 }
2614
2615 std::wstring help_producer_command(command_context& ctx)
2616 {
2617         if (ctx.parameters.size() == 0)
2618                 return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
2619         else
2620                 return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
2621 }
2622
2623 void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
2624 {
2625         sink.short_description(L"Show online help for consumers.");
2626         sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
2627         sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
2628         sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
2629         sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
2630 }
2631
2632 std::wstring help_consumer_command(command_context& ctx)
2633 {
2634         if (ctx.parameters.size() == 0)
2635                 return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
2636         else
2637                 return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
2638 }
2639
2640 void bye_describer(core::help_sink& sink, const core::help_repository& repo)
2641 {
2642         sink.short_description(L"Disconnect the session.");
2643         sink.syntax(L"BYE");
2644         sink.para()
2645                 ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
2646                 ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
2647 }
2648
2649 std::wstring bye_command(command_context& ctx)
2650 {
2651         ctx.client->disconnect();
2652         return L"";
2653 }
2654
2655 void kill_describer(core::help_sink& sink, const core::help_repository& repo)
2656 {
2657         sink.short_description(L"Shutdown the server.");
2658         sink.syntax(L"KILL");
2659         sink.para()->text(L"Shuts the server down.");
2660 }
2661
2662 std::wstring kill_command(command_context& ctx)
2663 {
2664         ctx.shutdown_server_now.set_value(false);       //false for not attempting to restart
2665         return L"202 KILL OK\r\n";
2666 }
2667
2668 void restart_describer(core::help_sink& sink, const core::help_repository& repo)
2669 {
2670         sink.short_description(L"Shutdown the server with restart exit code.");
2671         sink.syntax(L"RESTART");
2672         sink.para()
2673                 ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
2674                 ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
2675 }
2676
2677 std::wstring restart_command(command_context& ctx)
2678 {
2679         ctx.shutdown_server_now.set_value(true);        //true for attempting to restart
2680         return L"202 RESTART OK\r\n";
2681 }
2682
2683 void lock_describer(core::help_sink& sink, const core::help_repository& repo)
2684 {
2685         sink.short_description(L"Lock or unlock access to a channel.");
2686         sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
2687         sink.para()->text(L"Allows for exclusive access to a channel.");
2688         sink.para()->text(L"Examples:");
2689         sink.example(L"LOCK 1 ACQUIRE secret");
2690         sink.example(L"LOCK 1 RELEASE");
2691         sink.example(L"LOCK 1 CLEAR");
2692 }
2693
2694 std::wstring lock_command(command_context& ctx)
2695 {
2696         int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
2697         auto lock = ctx.channels.at(channel_index).lock;
2698         auto command = boost::to_upper_copy(ctx.parameters.at(1));
2699
2700         if (command == L"ACQUIRE")
2701         {
2702                 std::wstring lock_phrase = ctx.parameters.at(2);
2703
2704                 //TODO: read options
2705
2706                 //just lock one channel
2707                 if (!lock->try_lock(lock_phrase, ctx.client))
2708                         return L"503 LOCK ACQUIRE FAILED\r\n";
2709
2710                 return L"202 LOCK ACQUIRE OK\r\n";
2711         }
2712         else if (command == L"RELEASE")
2713         {
2714                 lock->release_lock(ctx.client);
2715                 return L"202 LOCK RELEASE OK\r\n";
2716         }
2717         else if (command == L"CLEAR")
2718         {
2719                 std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
2720                 std::wstring client_override_phrase;
2721
2722                 if (!override_phrase.empty())
2723                         client_override_phrase = ctx.parameters.at(2);
2724
2725                 //just clear one channel
2726                 if (client_override_phrase != override_phrase)
2727                         return L"503 LOCK CLEAR FAILED\r\n";
2728
2729                 lock->clear_locks();
2730
2731                 return L"202 LOCK CLEAR OK\r\n";
2732         }
2733
2734         CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
2735 }
2736
2737 void register_commands(amcp_command_repository& repo)
2738 {
2739         repo.register_channel_command(  L"Basic Commands",              L"LOADBG",                                      loadbg_describer,                                       loadbg_command,                                 1);
2740         repo.register_channel_command(  L"Basic Commands",              L"LOAD",                                        load_describer,                                         load_command,                                   1);
2741         repo.register_channel_command(  L"Basic Commands",              L"PLAY",                                        play_describer,                                         play_command,                                   0);
2742         repo.register_channel_command(  L"Basic Commands",              L"PAUSE",                                       pause_describer,                                        pause_command,                                  0);
2743         repo.register_channel_command(  L"Basic Commands",              L"RESUME",                                      resume_describer,                                       resume_command,                                 0);
2744         repo.register_channel_command(  L"Basic Commands",              L"STOP",                                        stop_describer,                                         stop_command,                                   0);
2745         repo.register_channel_command(  L"Basic Commands",              L"CLEAR",                                       clear_describer,                                        clear_command,                                  0);
2746         repo.register_channel_command(  L"Basic Commands",              L"CALL",                                        call_describer,                                         call_command,                                   1);
2747         repo.register_channel_command(  L"Basic Commands",              L"SWAP",                                        swap_describer,                                         swap_command,                                   1);
2748         repo.register_channel_command(  L"Basic Commands",              L"ADD",                                         add_describer,                                          add_command,                                    1);
2749         repo.register_channel_command(  L"Basic Commands",              L"REMOVE",                                      remove_describer,                                       remove_command,                                 0);
2750         repo.register_channel_command(  L"Basic Commands",              L"PRINT",                                       print_describer,                                        print_command,                                  0);
2751         repo.register_command(                  L"Basic Commands",              L"LOG LEVEL",                           log_level_describer,                            log_level_command,                              1);
2752         repo.register_channel_command(  L"Basic Commands",              L"SET",                                         set_describer,                                          set_command,                                    2);
2753         repo.register_command(                  L"Basic Commands",              L"LOCK",                                        lock_describer,                                         lock_command,                                   2);
2754
2755         repo.register_command(                  L"Data Commands",               L"DATA STORE",                          data_store_describer,                           data_store_command,                             2);
2756         repo.register_command(                  L"Data Commands",               L"DATA RETRIEVE",                       data_retrieve_describer,                        data_retrieve_command,                  1);
2757         repo.register_command(                  L"Data Commands",               L"DATA LIST",                           data_list_describer,                            data_list_command,                              0);
2758         repo.register_command(                  L"Data Commands",               L"DATA REMOVE",                         data_remove_describer,                          data_remove_command,                    1);
2759
2760         repo.register_channel_command(  L"Template Commands",   L"CG ADD",                                      cg_add_describer,                                       cg_add_command,                                 3);
2761         repo.register_channel_command(  L"Template Commands",   L"CG PLAY",                                     cg_play_describer,                                      cg_play_command,                                1);
2762         repo.register_channel_command(  L"Template Commands",   L"CG STOP",                                     cg_stop_describer,                                      cg_stop_command,                                1);
2763         repo.register_channel_command(  L"Template Commands",   L"CG NEXT",                                     cg_next_describer,                                      cg_next_command,                                1);
2764         repo.register_channel_command(  L"Template Commands",   L"CG REMOVE",                           cg_remove_describer,                            cg_remove_command,                              1);
2765         repo.register_channel_command(  L"Template Commands",   L"CG CLEAR",                            cg_clear_describer,                                     cg_clear_command,                               0);
2766         repo.register_channel_command(  L"Template Commands",   L"CG UPDATE",                           cg_update_describer,                            cg_update_command,                              2);
2767         repo.register_channel_command(  L"Template Commands",   L"CG INVOKE",                           cg_invoke_describer,                            cg_invoke_command,                              2);
2768         repo.register_channel_command(  L"Template Commands",   L"CG INFO",                                     cg_info_describer,                                      cg_info_command,                                0);
2769
2770         repo.register_channel_command(  L"Mixer Commands",              L"MIXER KEYER",                         mixer_keyer_describer,                          mixer_keyer_command,                    0);
2771         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CHROMA",                        mixer_chroma_describer,                         mixer_chroma_command,                   0);
2772         repo.register_channel_command(  L"Mixer Commands",              L"MIXER BLEND",                         mixer_blend_describer,                          mixer_blend_command,                    0);
2773         repo.register_channel_command(  L"Mixer Commands",              L"MIXER OPACITY",                       mixer_opacity_describer,                        mixer_opacity_command,                  0);
2774         repo.register_channel_command(  L"Mixer Commands",              L"MIXER BRIGHTNESS",            mixer_brightness_describer,                     mixer_brightness_command,               0);
2775         repo.register_channel_command(  L"Mixer Commands",              L"MIXER SATURATION",            mixer_saturation_describer,                     mixer_saturation_command,               0);
2776         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CONTRAST",                      mixer_contrast_describer,                       mixer_contrast_command,                 0);
2777         repo.register_channel_command(  L"Mixer Commands",              L"MIXER LEVELS",                        mixer_levels_describer,                         mixer_levels_command,                   0);
2778         repo.register_channel_command(  L"Mixer Commands",              L"MIXER FILL",                          mixer_fill_describer,                           mixer_fill_command,                             0);
2779         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CLIP",                          mixer_clip_describer,                           mixer_clip_command,                             0);
2780         repo.register_channel_command(  L"Mixer Commands",              L"MIXER ANCHOR",                        mixer_anchor_describer,                         mixer_anchor_command,                   0);
2781         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CROP",                          mixer_crop_describer,                           mixer_crop_command,                             0);
2782         repo.register_channel_command(  L"Mixer Commands",              L"MIXER ROTATION",                      mixer_rotation_describer,                       mixer_rotation_command,                 0);
2783         repo.register_channel_command(  L"Mixer Commands",              L"MIXER PERSPECTIVE",           mixer_perspective_describer,            mixer_perspective_command,              0);
2784         repo.register_channel_command(  L"Mixer Commands",              L"MIXER MIPMAP",                        mixer_mipmap_describer,                         mixer_mipmap_command,                   0);
2785         repo.register_channel_command(  L"Mixer Commands",              L"MIXER VOLUME",                        mixer_volume_describer,                         mixer_volume_command,                   0);
2786         repo.register_channel_command(  L"Mixer Commands",              L"MIXER MASTERVOLUME",          mixer_mastervolume_describer,           mixer_mastervolume_command,             0);
2787         repo.register_channel_command(  L"Mixer Commands",              L"MIXER GRID",                          mixer_grid_describer,                           mixer_grid_command,                             1);
2788         repo.register_channel_command(  L"Mixer Commands",              L"MIXER COMMIT",                        mixer_commit_describer,                         mixer_commit_command,                   0);
2789         repo.register_channel_command(  L"Mixer Commands",              L"MIXER CLEAR",                         mixer_clear_describer,                          mixer_clear_command,                    0);
2790         repo.register_command(                  L"Mixer Commands",              L"CHANNEL_GRID",                        channel_grid_describer,                         channel_grid_command,                   0);
2791
2792         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL LIST",                      thumbnail_list_describer,                       thumbnail_list_command,                 0);
2793         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL RETRIEVE",          thumbnail_retrieve_describer,           thumbnail_retrieve_command,             1);
2794         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL GENERATE",          thumbnail_generate_describer,           thumbnail_generate_command,             1);
2795         repo.register_command(                  L"Thumbnail Commands",  L"THUMBNAIL GENERATE_ALL",      thumbnail_generateall_describer,        thumbnail_generateall_command,  0);
2796
2797         repo.register_command(                  L"Query Commands",              L"CINF",                                        cinf_describer,                                         cinf_command,                                   1);
2798         repo.register_command(                  L"Query Commands",              L"CLS",                                         cls_describer,                                          cls_command,                                    0);
2799         repo.register_command(                  L"Query Commands",              L"TLS",                                         tls_describer,                                          tls_command,                                    0);
2800         repo.register_command(                  L"Query Commands",              L"VERSION",                                     version_describer,                                      version_command,                                0);
2801         repo.register_command(                  L"Query Commands",              L"INFO",                                        info_describer,                                         info_command,                                   0);
2802         repo.register_channel_command(  L"Query Commands",              L"INFO",                                        info_channel_describer,                         info_channel_command,                   0);
2803         repo.register_command(                  L"Query Commands",              L"INFO TEMPLATE",                       info_template_describer,                        info_template_command,                  1);
2804         repo.register_command(                  L"Query Commands",              L"INFO CONFIG",                         info_config_describer,                          info_config_command,                    0);
2805         repo.register_command(                  L"Query Commands",              L"INFO PATHS",                          info_paths_describer,                           info_paths_command,                             0);
2806         repo.register_command(                  L"Query Commands",              L"INFO SYSTEM",                         info_system_describer,                          info_system_command,                    0);
2807         repo.register_command(                  L"Query Commands",              L"INFO SERVER",                         info_server_describer,                          info_server_command,                    0);
2808         repo.register_command(                  L"Query Commands",              L"INFO QUEUES",                         info_queues_describer,                          info_queues_command,                    0);
2809         repo.register_command(                  L"Query Commands",              L"INFO THREADS",                        info_threads_describer,                         info_threads_command,                   0);
2810         repo.register_channel_command(  L"Query Commands",              L"INFO DELAY",                          info_delay_describer,                           info_delay_command,                             0);
2811         repo.register_command(                  L"Query Commands",              L"DIAG",                                        diag_describer,                                         diag_command,                                   0);
2812         repo.register_command(                  L"Query Commands",              L"BYE",                                         bye_describer,                                          bye_command,                                    0);
2813         repo.register_command(                  L"Query Commands",              L"KILL",                                        kill_describer,                                         kill_command,                                   0);
2814         repo.register_command(                  L"Query Commands",              L"RESTART",                                     restart_describer,                                      restart_command,                                0);
2815         repo.register_command(                  L"Query Commands",              L"HELP",                                        help_describer,                                         help_command,                                   0);
2816         repo.register_command(                  L"Query Commands",              L"HELP PRODUCER",                       help_producer_describer,                        help_producer_command,                  0);
2817         repo.register_command(                  L"Query Commands",              L"HELP CONSUMER",                       help_consumer_describer,                        help_consumer_command,                  0);
2818 }
2819
2820 }       //namespace amcp
2821 }}      //namespace caspar