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