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