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