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