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