]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPCommandsImpl.cpp
* Merged layer_producer and channel_producer from 2.0 to the reroute module (replacin...
[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 #include "AMCPProtocolStrategy.h"
30
31 #include <common/env.h>
32
33 #include <common/log.h>
34 #include <common/param.h>
35 #include <common/os/system_info.h>
36 #include <common/os/filesystem.h>
37 #include <common/base64.h>
38
39 #include <core/producer/frame_producer.h>
40 #include <core/video_format.h>
41 #include <core/producer/transition/transition_producer.h>
42 #include <core/frame/frame_transform.h>
43 #include <core/producer/stage.h>
44 #include <core/producer/layer.h>
45 #include <core/mixer/mixer.h>
46 #include <core/consumer/output.h>
47 #include <core/thumbnail_generator.h>
48 #include <core/producer/media_info/media_info.h>
49 #include <core/producer/media_info/media_info_repository.h>
50 #include <core/diagnostics/call_context.h>
51 #include <core/diagnostics/osd_graph.h>
52 #include <core/system_info_provider.h>
53
54 #include <algorithm>
55 #include <locale>
56 #include <fstream>
57 #include <memory>
58 #include <cctype>
59 #include <future>
60
61 #include <boost/date_time/posix_time/posix_time.hpp>
62 #include <boost/lexical_cast.hpp>
63 #include <boost/algorithm/string.hpp>
64 #include <boost/filesystem.hpp>
65 #include <boost/filesystem/fstream.hpp>
66 #include <boost/regex.hpp>
67 #include <boost/property_tree/xml_parser.hpp>
68 #include <boost/locale.hpp>
69 #include <boost/range/adaptor/transformed.hpp>
70 #include <boost/range/algorithm/copy.hpp>
71 #include <boost/archive/iterators/base64_from_binary.hpp>
72 #include <boost/archive/iterators/insert_linebreaks.hpp>
73 #include <boost/archive/iterators/transform_width.hpp>
74
75 #include <tbb/concurrent_unordered_map.h>
76
77 /* Return codes
78
79 102 [action]                    Information that [action] has happened
80 101 [action]                    Information that [action] has happened plus one row of data  
81
82 202 [command] OK                [command] has been executed
83 201 [command] OK                [command] has been executed, plus one row of data  
84 200 [command] OK                [command] has been executed, plus multiple lines of data. ends with an empty line
85
86 400 ERROR                               the command could not be understood
87 401 [command] ERROR             invalid/missing channel
88 402 [command] ERROR             parameter missing
89 403 [command] ERROR             invalid parameter  
90 404 [command] ERROR             file not found
91
92 500 FAILED                              internal error
93 501 [command] FAILED    internal error
94 502 [command] FAILED    could not read file
95 503 [command] FAILED    access denied
96
97 600 [command] FAILED    [command] not implemented
98 */
99
100 namespace caspar { namespace protocol {
101
102 using namespace core;
103
104 std::wstring read_file_base64(const boost::filesystem::path& file)
105 {
106         using namespace boost::archive::iterators;
107
108         boost::filesystem::ifstream filestream(file, std::ios::binary);
109
110         if (!filestream)
111                 return L"";
112
113         auto length = boost::filesystem::file_size(file);
114         std::vector<char> bytes;
115         bytes.resize(length);
116         filestream.read(bytes.data(), length);
117
118         std::string result(to_base64(bytes.data(), length));
119         return std::wstring(result.begin(), result.end());
120 }
121
122 std::wstring read_utf8_file(const boost::filesystem::path& file)
123 {
124         std::wstringstream result;
125         boost::filesystem::wifstream filestream(file);
126
127         if (filestream) 
128         {
129                 // Consume BOM first
130                 filestream.get();
131                 // read all data
132                 result << filestream.rdbuf();
133         }
134
135         return result.str();
136 }
137
138 std::wstring read_latin1_file(const boost::filesystem::path& file)
139 {
140         boost::locale::generator gen;
141         gen.locale_cache_enabled(true);
142         gen.categories(boost::locale::codepage_facet);
143
144         std::stringstream result_stream;
145         boost::filesystem::ifstream filestream(file);
146         filestream.imbue(gen("en_US.ISO8859-1"));
147
148         if (filestream)
149         {
150                 // read all data
151                 result_stream << filestream.rdbuf();
152         }
153
154         std::string result = result_stream.str();
155         std::wstring widened_result;
156
157         // The first 255 codepoints in unicode is the same as in latin1
158         boost::copy(
159                 result | boost::adaptors::transformed(
160                                 [](char c) { return static_cast<unsigned char>(c); }),
161                 std::back_inserter(widened_result));
162
163         return widened_result;
164 }
165
166 std::wstring read_file(const boost::filesystem::path& file)
167 {
168         static const uint8_t BOM[] = {0xef, 0xbb, 0xbf};
169
170         if (!boost::filesystem::exists(file))
171         {
172                 return L"";
173         }
174
175         if (boost::filesystem::file_size(file) >= 3)
176         {
177                 boost::filesystem::ifstream bom_stream(file);
178
179                 char header[3];
180                 bom_stream.read(header, 3);
181                 bom_stream.close();
182
183                 if (std::memcmp(BOM, header, 3) == 0)
184                         return read_utf8_file(file);
185         }
186
187         return read_latin1_file(file);
188 }
189
190 std::wstring MediaInfo(const boost::filesystem::path& path, const spl::shared_ptr<media_info_repository>& media_info_repo)
191 {
192         if (!boost::filesystem::is_regular_file(path))
193                 return L"";
194
195         auto media_info = media_info_repo->get(path.wstring());
196
197         if (!media_info)
198                 return L"";
199
200         auto is_not_digit = [](char c){ return std::isdigit(c) == 0; };
201
202         auto relativePath = boost::filesystem::path(path.wstring().substr(env::media_folder().size() - 1, path.wstring().size()));
203
204         auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(path)));
205         writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), is_not_digit), writeTimeStr.end());
206         auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
207
208         auto sizeStr = boost::lexical_cast<std::wstring>(boost::filesystem::file_size(path));
209         sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), is_not_digit), sizeStr.end());
210         auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
211
212         auto str = relativePath.replace_extension(L"").generic_wstring();
213         if (str[0] == '\\' || str[0] == '/')
214                 str = std::wstring(str.begin() + 1, str.end());
215
216         return std::wstring()
217                 + L"\"" + str +
218                 + L"\" " + media_info->clip_type +
219                 + L" " + sizeStr +
220                 + L" " + writeTimeWStr +
221                 + L" " + boost::lexical_cast<std::wstring>(media_info->duration) +
222                 + L" " + boost::lexical_cast<std::wstring>(media_info->time_base.numerator()) + L"/" + boost::lexical_cast<std::wstring>(media_info->time_base.denominator())
223                 + L"\r\n";
224 }
225
226 std::wstring ListMedia(const spl::shared_ptr<media_info_repository>& media_info_repo)
227 {       
228         std::wstringstream replyString;
229         for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
230                 replyString << MediaInfo(itr->path(), media_info_repo);
231         
232         return boost::to_upper_copy(replyString.str());
233 }
234
235 std::wstring ListTemplates(const spl::shared_ptr<core::cg_producer_registry>& cg_registry)
236 {
237         std::wstringstream replyString;
238
239         for (boost::filesystem::recursive_directory_iterator itr(env::template_folder()), end; itr != end; ++itr)
240         {               
241                 if(boost::filesystem::is_regular_file(itr->path()) && cg_registry->is_cg_extension(itr->path().extension().wstring()))
242                 {
243                         auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::template_folder().size()-1, itr->path().wstring().size()));
244
245                         auto writeTimeStr = boost::posix_time::to_iso_string(boost::posix_time::from_time_t(boost::filesystem::last_write_time(itr->path())));
246                         writeTimeStr.erase(std::remove_if(writeTimeStr.begin(), writeTimeStr.end(), [](char c){ return std::isdigit(c) == 0;}), writeTimeStr.end());
247                         auto writeTimeWStr = std::wstring(writeTimeStr.begin(), writeTimeStr.end());
248
249                         auto sizeStr = boost::lexical_cast<std::string>(boost::filesystem::file_size(itr->path()));
250                         sizeStr.erase(std::remove_if(sizeStr.begin(), sizeStr.end(), [](char c){ return std::isdigit(c) == 0;}), sizeStr.end());
251
252                         auto sizeWStr = std::wstring(sizeStr.begin(), sizeStr.end());
253
254                         auto dir = relativePath.parent_path();
255                         auto file = boost::to_upper_copy(relativePath.filename().wstring());
256                         relativePath = dir / file;
257                                                 
258                         auto str = relativePath.replace_extension(L"").generic_wstring();
259                         boost::trim_if(str, boost::is_any_of("\\/"));
260
261                         replyString << L"\"" << str
262                                                 << L"\" " << sizeWStr
263                                                 << L" " << writeTimeWStr
264                                                 << L"\r\n";
265                 }
266         }
267         return replyString.str();
268 }
269
270 namespace amcp {
271         
272 void AMCPCommand::SendReply()
273 {
274         if(replyString_.empty())
275                 return;
276
277         client_->send(std::move(replyString_));
278 }
279
280 bool DiagnosticsCommand::DoExecute()
281 {       
282         try
283         {
284                 core::diagnostics::osd::show_graphs(true);
285
286                 SetReplyString(L"202 DIAG OK\r\n");
287
288                 return true;
289         }
290         catch(...)
291         {
292                 CASPAR_LOG_CURRENT_EXCEPTION();
293                 SetReplyString(L"502 DIAG FAILED\r\n");
294                 return false;
295         }
296 }
297
298 bool ChannelGridCommand::DoExecute()
299 {
300         int index = 1;
301         auto self = channels().back().channel;
302         
303         core::diagnostics::scoped_call_context save;
304         core::diagnostics::call_context::for_thread().video_channel = channels().size();
305
306         std::vector<std::wstring> params;
307         params.push_back(L"SCREEN");
308         params.push_back(L"0");
309         params.push_back(L"NAME");
310         params.push_back(L"Channel Grid Window");
311         auto screen = create_consumer(params, &self->stage());
312
313         self->output().add(screen);
314
315         for (auto& channel : channels())
316         {
317                 if(channel.channel != self)
318                 {
319                         core::diagnostics::call_context::for_thread().layer = index;
320                         auto producer = create_producer(get_dependencies(channel.channel), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()));
321                         self->stage().load(index, producer, false);
322                         self->stage().play(index);
323                         index++;
324                 }
325         }
326
327         MixerCommand mixer(client(), channels().back(), self->index(), -1);
328         auto num_channels = channels().size() - 1;
329         int square_side_length = std::ceil(std::sqrt(num_channels));
330         mixer.parameters().push_back(L"GRID");
331         mixer.parameters().push_back(boost::lexical_cast<std::wstring>(square_side_length));
332         mixer.Execute();
333
334         return true;
335 }
336
337 bool CallCommand::DoExecute()
338 {       
339         //Perform loading of the clip
340         try
341         {
342                 auto result = channel()->stage().call(layer_index(), parameters());
343                 
344                 // TODO: because of std::async deferred timed waiting does not work
345
346                 /*auto wait_res = result.wait_for(std::chrono::seconds(2));
347                 if (wait_res == std::future_status::timeout)
348                         CASPAR_THROW_EXCEPTION(timed_out());*/
349                                 
350                 std::wstringstream replyString;
351                 if(result.get().empty())
352                         replyString << L"202 CALL OK\r\n";
353                 else
354                         replyString << L"201 CALL OK\r\n" << result.get() << L"\r\n";
355                 
356                 SetReplyString(replyString.str());
357
358                 return true;
359         }
360         catch(...)
361         {
362                 CASPAR_LOG_CURRENT_EXCEPTION();
363                 SetReplyString(L"502 CALL FAILED\r\n");
364                 return false;
365         }
366 }
367
368 tbb::concurrent_unordered_map<int, std::vector<stage::transform_tuple_t>> deferred_transforms;
369
370 core::frame_transform MixerCommand::get_current_transform()
371 {
372         return channel()->stage().get_current_transform(layer_index()).get();
373 }
374
375 bool MixerCommand::DoExecute()
376 {       
377         //Perform loading of the clip
378         try
379         {       
380                 bool defer = boost::iequals(parameters().back(), L"DEFER");
381                 if(defer)
382                         parameters().pop_back();
383
384                 std::vector<stage::transform_tuple_t> transforms;
385
386                 if(boost::iequals(parameters()[0], L"KEYER") || boost::iequals(parameters()[0], L"IS_KEY"))
387                 {
388                         if (parameters().size() == 1)
389                                 return reply_value([](const frame_transform& t) { return t.image_transform.is_key ? 1 : 0; });
390
391                         bool value = boost::lexical_cast<int>(parameters().at(1));
392                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
393                         {
394                                 transform.image_transform.is_key = value;
395                                 return transform;                                       
396                         }, 0, L"linear"));
397                 }
398                 else if(boost::iequals(parameters()[0], L"OPACITY"))
399                 {
400                         if (parameters().size() == 1)
401                                 return reply_value([](const frame_transform& t) { return t.image_transform.opacity; });
402
403                         int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
404                         std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
405
406                         double value = boost::lexical_cast<double>(parameters().at(1));
407                         
408                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
409                         {
410                                 transform.image_transform.opacity = value;
411                                 return transform;                                       
412                         }, duration, tween));
413                 }
414                 else if (boost::iequals(parameters()[0], L"ANCHOR"))
415                 {
416                         if (parameters().size() == 1)
417                         {
418                                 auto transform = get_current_transform().image_transform;
419                                 auto anchor = transform.anchor;
420                                 SetReplyString(
421                                                 L"201 MIXER OK\r\n"
422                                                 + boost::lexical_cast<std::wstring>(anchor[0]) + L" "
423                                                 + boost::lexical_cast<std::wstring>(anchor[1]) + L"\r\n");
424                                 return true;
425                         }
426
427                         int duration = parameters().size() > 3 ? boost::lexical_cast<int>(parameters()[3]) : 0;
428                         std::wstring tween = parameters().size() > 4 ? parameters()[4] : L"linear";
429                         double x = boost::lexical_cast<double>(parameters().at(1));
430                         double y = boost::lexical_cast<double>(parameters().at(2));
431
432                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) mutable -> frame_transform
433                         {
434                                 transform.image_transform.anchor[0] = x;
435                                 transform.image_transform.anchor[1] = y;
436                                 return transform;
437                         }, duration, tween));
438                 }
439                 else if (boost::iequals(parameters()[0], L"FILL") || boost::iequals(parameters()[0], L"FILL_RECT"))
440                 {
441                         if (parameters().size() == 1)
442                         {
443                                 auto transform = get_current_transform().image_transform;
444                                 auto translation = transform.fill_translation;
445                                 auto scale = transform.fill_scale;
446                                 SetReplyString(
447                                                 L"201 MIXER OK\r\n"
448                                                 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
449                                                 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
450                                                 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
451                                                 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n");
452                                 return true;
453                         }
454
455                         int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
456                         std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
457                         double x        = boost::lexical_cast<double>(parameters().at(1));
458                         double y        = boost::lexical_cast<double>(parameters().at(2));
459                         double x_s      = boost::lexical_cast<double>(parameters().at(3));
460                         double y_s      = boost::lexical_cast<double>(parameters().at(4));
461
462                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) mutable -> frame_transform
463                         {
464                                 transform.image_transform.fill_translation[0]   = x;
465                                 transform.image_transform.fill_translation[1]   = y;
466                                 transform.image_transform.fill_scale[0]                 = x_s;
467                                 transform.image_transform.fill_scale[1]                 = y_s;
468                                 return transform;
469                         }, duration, tween));
470                 }
471                 else if(boost::iequals(parameters()[0], L"CLIP") || boost::iequals(parameters()[0], L"CLIP_RECT"))
472                 {
473                         if (parameters().size() == 1)
474                         {
475                                 auto transform = get_current_transform().image_transform;
476                                 auto translation = transform.clip_translation;
477                                 auto scale = transform.clip_scale;
478                                 SetReplyString(
479                                                 L"201 MIXER OK\r\n"
480                                                 + boost::lexical_cast<std::wstring>(translation[0]) + L" "
481                                                 + boost::lexical_cast<std::wstring>(translation[1]) + L" "
482                                                 + boost::lexical_cast<std::wstring>(scale[0]) + L" "
483                                                 + boost::lexical_cast<std::wstring>(scale[1]) + L"\r\n");
484                                 return true;
485                         }
486
487                         int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
488                         std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
489                         double x        = boost::lexical_cast<double>(parameters().at(1));
490                         double y        = boost::lexical_cast<double>(parameters().at(2));
491                         double x_s      = boost::lexical_cast<double>(parameters().at(3));
492                         double y_s      = boost::lexical_cast<double>(parameters().at(4));
493
494                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
495                         {
496                                 transform.image_transform.clip_translation[0]   = x;
497                                 transform.image_transform.clip_translation[1]   = y;
498                                 transform.image_transform.clip_scale[0]                 = x_s;
499                                 transform.image_transform.clip_scale[1]                 = y_s;
500                                 return transform;
501                         }, duration, tween));
502                 }
503                 else if (boost::iequals(parameters()[0], L"CROP"))
504                 {
505                         if (parameters().size() == 1)
506                         {
507                                 auto crop = get_current_transform().image_transform.crop;
508                                 SetReplyString(
509                                         L"201 MIXER OK\r\n"
510                                         + boost::lexical_cast<std::wstring>(crop.ul[0]) + L" "
511                                         + boost::lexical_cast<std::wstring>(crop.ul[1]) + L" "
512                                         + boost::lexical_cast<std::wstring>(crop.lr[0]) + L" "
513                                         + boost::lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n");
514                                 return true;
515                         }
516
517                         int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters()[5]) : 0;
518                         std::wstring tween = parameters().size() > 6 ? parameters()[6] : L"linear";
519                         double ul_x = boost::lexical_cast<double>(parameters().at(1));
520                         double ul_y = boost::lexical_cast<double>(parameters().at(2));
521                         double lr_x = boost::lexical_cast<double>(parameters().at(3));
522                         double lr_y = boost::lexical_cast<double>(parameters().at(4));
523
524                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
525                         {
526                                 transform.image_transform.crop.ul[0] = ul_x;
527                                 transform.image_transform.crop.ul[1] = ul_y;
528                                 transform.image_transform.crop.lr[0] = lr_x;
529                                 transform.image_transform.crop.lr[1] = lr_y;
530                                 return transform;
531                         }, duration, tween));
532                 }
533                 else if (boost::iequals(parameters()[0], L"PERSPECTIVE"))
534                 {
535                         if (parameters().size() == 1)
536                         {
537                                 auto perspective = get_current_transform().image_transform.perspective;
538                                 SetReplyString(
539                                                 L"201 MIXER OK\r\n"
540                                                 + boost::lexical_cast<std::wstring>(perspective.ul[0]) + L" "
541                                                 + boost::lexical_cast<std::wstring>(perspective.ul[1]) + L" "
542                                                 + boost::lexical_cast<std::wstring>(perspective.ur[0]) + L" "
543                                                 + boost::lexical_cast<std::wstring>(perspective.ur[1]) + L" "
544                                                 + boost::lexical_cast<std::wstring>(perspective.lr[0]) + L" "
545                                                 + boost::lexical_cast<std::wstring>(perspective.lr[1]) + L" "
546                                                 + boost::lexical_cast<std::wstring>(perspective.ll[0]) + L" "
547                                                 + boost::lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n");
548                                 return true;
549                         }
550
551                         int duration = parameters().size() > 9 ? boost::lexical_cast<int>(parameters()[9]) : 0;
552                         std::wstring tween = parameters().size() > 10 ? parameters()[10] : L"linear";
553                         double ul_x = boost::lexical_cast<double>(parameters().at(1));
554                         double ul_y = boost::lexical_cast<double>(parameters().at(2));
555                         double ur_x = boost::lexical_cast<double>(parameters().at(3));
556                         double ur_y = boost::lexical_cast<double>(parameters().at(4));
557                         double lr_x = boost::lexical_cast<double>(parameters().at(5));
558                         double lr_y = boost::lexical_cast<double>(parameters().at(6));
559                         double ll_x = boost::lexical_cast<double>(parameters().at(7));
560                         double ll_y = boost::lexical_cast<double>(parameters().at(8));
561
562                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
563                         {
564                                 transform.image_transform.perspective.ul[0] = ul_x;
565                                 transform.image_transform.perspective.ul[1] = ul_y;
566                                 transform.image_transform.perspective.ur[0] = ur_x;
567                                 transform.image_transform.perspective.ur[1] = ur_y;
568                                 transform.image_transform.perspective.lr[0] = lr_x;
569                                 transform.image_transform.perspective.lr[1] = lr_y;
570                                 transform.image_transform.perspective.ll[0] = ll_x;
571                                 transform.image_transform.perspective.ll[1] = ll_y;
572                                 return transform;
573                         }, duration, tween));
574                 }
575                 else if (boost::iequals(parameters()[0], L"GRID"))
576                 {
577                         int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
578                         std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
579                         int n = boost::lexical_cast<int>(parameters().at(1));
580                         double delta = 1.0/static_cast<double>(n);
581                         for(int x = 0; x < n; ++x)
582                         {
583                                 for(int y = 0; y < n; ++y)
584                                 {
585                                         int index = x+y*n+1;
586                                         transforms.push_back(stage::transform_tuple_t(index, [=](frame_transform transform) -> frame_transform
587                                         {               
588                                                 transform.image_transform.fill_translation[0]   = x*delta;
589                                                 transform.image_transform.fill_translation[1]   = y*delta;
590                                                 transform.image_transform.fill_scale[0]                 = delta;
591                                                 transform.image_transform.fill_scale[1]                 = delta;
592                                                 transform.image_transform.clip_translation[0]   = x*delta;
593                                                 transform.image_transform.clip_translation[1]   = y*delta;
594                                                 transform.image_transform.clip_scale[0]                 = delta;
595                                                 transform.image_transform.clip_scale[1]                 = delta;                        
596                                                 return transform;
597                                         }, duration, tween));
598                                 }
599                         }
600                 }
601                 else if (boost::iequals(parameters()[0], L"MIPMAP"))
602                 {
603                         if (parameters().size() == 1)
604                                 return reply_value([](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
605
606                         bool value = boost::lexical_cast<int>(parameters().at(1));
607                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
608                         {
609                                 transform.image_transform.use_mipmap = value;
610                                 return transform;
611                         }, 0, L"linear"));
612                 }
613                 else if(boost::iequals(parameters()[0], L"BLEND"))
614                 {
615                         if (parameters().size() == 1)
616                                 return reply_value([](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
617
618                         auto value = get_blend_mode(parameters().at(1));
619                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
620                         {
621                                 transform.image_transform.blend_mode = value;
622                                 return transform;
623                         }, 0, L"linear"));
624                 }
625                 else if (boost::iequals(parameters()[0], L"CHROMA"))
626                 {
627                         if (parameters().size() == 1)
628                         {
629                                 auto chroma = get_current_transform().image_transform.chroma;
630                                 SetReplyString(
631                                         L"201 MIXER OK\r\n"
632                                         + core::get_chroma_mode(chroma.key) + L" "
633                                         + boost::lexical_cast<std::wstring>(chroma.threshold) + L" "
634                                         + boost::lexical_cast<std::wstring>(chroma.softness) + L" "
635                                         + boost::lexical_cast<std::wstring>(chroma.spill) + L"\r\n");
636                                 return true;
637                         }
638
639                         int duration = parameters().size() > 5 ? boost::lexical_cast<int>(parameters().at(5)) : 0;
640                         std::wstring tween = parameters().size() > 6 ? parameters().at(6) : L"linear";
641
642                         core::chroma chroma;
643                         chroma.key                      = get_chroma_mode(parameters().at(1));
644                         chroma.threshold        = boost::lexical_cast<double>(parameters().at(2));
645                         chroma.softness         = boost::lexical_cast<double>(parameters().at(3));
646                         chroma.spill            = parameters().size() > 4 ? boost::lexical_cast<double>(parameters().at(4)) : 0.0;
647                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
648                         {
649                                 transform.image_transform.chroma = chroma;
650                                 return transform;
651                         }, duration, tween));
652                 }
653                 else if (boost::iequals(parameters()[0], L"MASTERVOLUME"))
654                 {
655                         if (parameters().size() == 1)
656                         {
657                                 auto volume = channel()->mixer().get_master_volume();
658                                 SetReplyString(L"201 MIXER OK\r\n"
659                                                 + boost::lexical_cast<std::wstring>(volume)+L"\r\n");
660                                 return true;
661                         }
662
663                         float master_volume = boost::lexical_cast<float>(parameters().at(1));
664                         channel()->mixer().set_master_volume(master_volume);
665                 }
666                 else if(boost::iequals(parameters()[0], L"BRIGHTNESS"))
667                 {
668                         if (parameters().size() == 1)
669                                 return reply_value([](const frame_transform& t) { return t.image_transform.brightness; });
670
671                         auto value = boost::lexical_cast<double>(parameters().at(1));
672                         int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
673                         std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
674                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
675                         {
676                                 transform.image_transform.brightness = value;
677                                 return transform;
678                         }, duration, tween));
679                 }
680                 else if(boost::iequals(parameters()[0], L"SATURATION"))
681                 {
682                         if (parameters().size() == 1)
683                                 return reply_value([](const frame_transform& t) { return t.image_transform.saturation; });
684
685                         auto value = boost::lexical_cast<double>(parameters().at(1));
686                         int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
687                         std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
688                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
689                         {
690                                 transform.image_transform.saturation = value;
691                                 return transform;
692                         }, duration, tween));   
693                 }
694                 else if (boost::iequals(parameters()[0], L"CONTRAST"))
695                 {
696                         if (parameters().size() == 1)
697                                 return reply_value([](const frame_transform& t) { return t.image_transform.contrast; });
698
699                         auto value = boost::lexical_cast<double>(parameters().at(1));
700                         int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
701                         std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
702                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
703                         {
704                                 transform.image_transform.contrast = value;
705                                 return transform;
706                         }, duration, tween));   
707                 }
708                 else if (boost::iequals(parameters()[0], L"ROTATION"))
709                 {
710                         static const double PI = 3.141592653589793;
711
712                         if (parameters().size() == 1)
713                                 return reply_value([](const frame_transform& t) { return t.image_transform.angle / PI * 180.0; });
714
715                         auto value = boost::lexical_cast<double>(parameters().at(1)) * PI / 180.0;
716                         int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
717                         std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
718                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
719                         {
720                                 transform.image_transform.angle = value;
721                                 return transform;
722                         }, duration, tween));
723                 }
724                 else if (boost::iequals(parameters()[0], L"LEVELS"))
725                 {
726                         if (parameters().size() == 1)
727                         {
728                                 auto levels = get_current_transform().image_transform.levels;
729                                 SetReplyString(L"201 MIXER OK\r\n"
730                                                 + boost::lexical_cast<std::wstring>(levels.min_input) + L" "
731                                                 + boost::lexical_cast<std::wstring>(levels.max_input) + L" "
732                                                 + boost::lexical_cast<std::wstring>(levels.gamma) + L" "
733                                                 + boost::lexical_cast<std::wstring>(levels.min_output) + L" "
734                                                 + boost::lexical_cast<std::wstring>(levels.max_output) + L"\r\n");
735                                 return true;
736                         }
737
738                         levels value;
739                         value.min_input  = boost::lexical_cast<double>(parameters().at(1));
740                         value.max_input  = boost::lexical_cast<double>(parameters().at(2));
741                         value.gamma              = boost::lexical_cast<double>(parameters().at(3));
742                         value.min_output = boost::lexical_cast<double>(parameters().at(4));
743                         value.max_output = boost::lexical_cast<double>(parameters().at(5));
744                         int duration = parameters().size() > 6 ? boost::lexical_cast<int>(parameters()[6]) : 0;
745                         std::wstring tween = parameters().size() > 7 ? parameters()[7] : L"linear";
746
747                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
748                         {
749                                 transform.image_transform.levels = value;
750                                 return transform;
751                         }, duration, tween));
752                 }
753                 else if(boost::iequals(parameters()[0], L"VOLUME"))
754                 {
755                         if (parameters().size() == 1)
756                                 return reply_value([](const frame_transform& t) { return t.audio_transform.volume; });
757
758                         int duration = parameters().size() > 2 ? boost::lexical_cast<int>(parameters()[2]) : 0;
759                         std::wstring tween = parameters().size() > 3 ? parameters()[3] : L"linear";
760                         double value = boost::lexical_cast<double>(parameters()[1]);
761
762                         transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
763                         {
764                                 transform.audio_transform.volume = value;
765                                 return transform;
766                         }, duration, tween));
767                 }
768                 else if(boost::iequals(parameters()[0], L"CLEAR"))
769                 {
770                         int layer = layer_index(std::numeric_limits<int>::max());
771
772                         if (layer == std::numeric_limits<int>::max())
773                         {
774                                 channel()->stage().clear_transforms();
775                         }
776                         else
777                         {
778                                 channel()->stage().clear_transforms(layer);
779                         }
780                 }
781                 else if(boost::iequals(parameters()[0], L"COMMIT"))
782                 {
783                         transforms = std::move(deferred_transforms[channel_index()]);
784                 }
785                 else
786                 {
787                         SetReplyString(L"404 MIXER ERROR\r\n");
788                         return false;
789                 }
790
791                 if(defer)
792                 {
793                         auto& defer_tranforms = deferred_transforms[channel_index()];
794                         defer_tranforms.insert(defer_tranforms.end(), transforms.begin(), transforms.end());
795                 }
796                 else
797                         channel()->stage().apply_transforms(transforms);
798         
799                 SetReplyString(L"202 MIXER OK\r\n");
800
801                 return true;
802         }
803         catch(file_not_found&)
804         {
805                 CASPAR_LOG_CURRENT_EXCEPTION();
806                 SetReplyString(L"404 MIXER ERROR\r\n");
807                 return false;
808         }
809         catch(...)
810         {
811                 CASPAR_LOG_CURRENT_EXCEPTION();
812                 SetReplyString(L"502 MIXER FAILED\r\n");
813                 return false;
814         }
815 }
816
817 bool SwapCommand::DoExecute()
818 {       
819         //Perform loading of the clip
820         try
821         {
822                 if(layer_index(-1) != -1)
823                 {
824                         std::vector<std::string> strs;
825                         boost::split(strs, parameters()[0], boost::is_any_of("-"));
826                         
827                         auto ch1 = channel();
828                         auto ch2 = channels().at(boost::lexical_cast<int>(strs.at(0))-1);
829
830                         int l1 = layer_index();
831                         int l2 = boost::lexical_cast<int>(strs.at(1));
832
833                         ch1->stage().swap_layer(l1, l2, ch2.channel->stage());
834                 }
835                 else
836                 {
837                         auto ch1 = channel();
838                         auto ch2 = channels().at(boost::lexical_cast<int>(parameters()[0])-1);
839                         ch1->stage().swap_layers(ch2.channel->stage());
840                 }
841                 
842                 SetReplyString(L"202 SWAP OK\r\n");
843
844                 return true;
845         }
846         catch(file_not_found&)
847         {
848                 CASPAR_LOG_CURRENT_EXCEPTION();
849                 SetReplyString(L"404 SWAP ERROR\r\n");
850                 return false;
851         }
852         catch(...)
853         {
854                 CASPAR_LOG_CURRENT_EXCEPTION();
855                 SetReplyString(L"502 SWAP FAILED\r\n");
856                 return false;
857         }
858 }
859
860 bool AddCommand::DoExecute()
861 {       
862         //Perform loading of the clip
863         try
864         {
865                 replace_placeholders(
866                                 L"<CLIENT_IP_ADDRESS>",
867                                 this->client()->address(),
868                                 parameters());
869
870                 core::diagnostics::scoped_call_context save;
871                 core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1;
872
873                 auto consumer = create_consumer(parameters(), &channel()->stage());
874                 channel()->output().add(layer_index(consumer->index()), consumer);
875         
876                 SetReplyString(L"202 ADD OK\r\n");
877
878                 return true;
879         }
880         catch(file_not_found&)
881         {
882                 CASPAR_LOG_CURRENT_EXCEPTION();
883                 SetReplyString(L"404 ADD ERROR\r\n");
884                 return false;
885         }
886         catch(...)
887         {
888                 CASPAR_LOG_CURRENT_EXCEPTION();
889                 SetReplyString(L"502 ADD FAILED\r\n");
890                 return false;
891         }
892 }
893
894 bool RemoveCommand::DoExecute()
895 {       
896         //Perform loading of the clip
897         try
898         {
899                 auto index = layer_index(std::numeric_limits<int>::min());
900                 if(index == std::numeric_limits<int>::min())
901                 {
902                         replace_placeholders(
903                                         L"<CLIENT_IP_ADDRESS>",
904                                         this->client()->address(),
905                                         parameters());
906
907                         index = create_consumer(parameters(), &channel()->stage())->index();
908                 }
909
910                 channel()->output().remove(index);
911
912                 SetReplyString(L"202 REMOVE OK\r\n");
913
914                 return true;
915         }
916         catch(file_not_found&)
917         {
918                 CASPAR_LOG_CURRENT_EXCEPTION();
919                 SetReplyString(L"404 REMOVE ERROR\r\n");
920                 return false;
921         }
922         catch(...)
923         {
924                 CASPAR_LOG_CURRENT_EXCEPTION();
925                 SetReplyString(L"502 REMOVE FAILED\r\n");
926                 return false;
927         }
928 }
929
930 bool LoadCommand::DoExecute()
931 {       
932         //Perform loading of the clip
933         try
934         {
935                 core::diagnostics::scoped_call_context save;
936                 core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1;
937                 core::diagnostics::call_context::for_thread().layer = layer_index();
938                 auto pFP = create_producer(get_dependencies(channel()), parameters());
939                 channel()->stage().load(layer_index(), pFP, true);
940         
941                 SetReplyString(L"202 LOAD OK\r\n");
942
943                 return true;
944         }
945         catch(file_not_found&)
946         {
947                 CASPAR_LOG_CURRENT_EXCEPTION();
948                 SetReplyString(L"404 LOAD ERROR\r\n");
949                 return false;
950         }
951         catch(...)
952         {
953                 CASPAR_LOG_CURRENT_EXCEPTION();
954                 SetReplyString(L"502 LOAD FAILED\r\n");
955                 return false;
956         }
957 }
958
959 bool LoadbgCommand::DoExecute()
960 {
961         transition_info transitionInfo;
962         
963         // TRANSITION
964
965         std::wstring message;
966         for(size_t n = 0; n < parameters().size(); ++n)
967                 message += boost::to_upper_copy(parameters()[n]) + L" ";
968                 
969         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)?.*)");
970         boost::wsmatch what;
971         if(boost::regex_match(message, what, expr))
972         {
973                 auto transition = what["TRANSITION"].str();
974                 transitionInfo.duration = boost::lexical_cast<size_t>(what["DURATION"].str());
975                 auto direction = what["DIRECTION"].matched ? what["DIRECTION"].str() : L"";
976                 auto tween = what["TWEEN"].matched ? what["TWEEN"].str() : L"";
977                 transitionInfo.tweener = tween;         
978
979                 if(transition == L"CUT")
980                         transitionInfo.type = transition_type::cut;
981                 else if(transition == L"MIX")
982                         transitionInfo.type = transition_type::mix;
983                 else if(transition == L"PUSH")
984                         transitionInfo.type = transition_type::push;
985                 else if(transition == L"SLIDE")
986                         transitionInfo.type = transition_type::slide;
987                 else if(transition == L"WIPE")
988                         transitionInfo.type = transition_type::wipe;
989                 
990                 if(direction == L"FROMLEFT")
991                         transitionInfo.direction = transition_direction::from_left;
992                 else if(direction == L"FROMRIGHT")
993                         transitionInfo.direction = transition_direction::from_right;
994                 else if(direction == L"LEFT")
995                         transitionInfo.direction = transition_direction::from_right;
996                 else if(direction == L"RIGHT")
997                         transitionInfo.direction = transition_direction::from_left;
998         }
999         
1000         //Perform loading of the clip
1001         try
1002         {
1003                 core::diagnostics::scoped_call_context save;
1004                 core::diagnostics::call_context::for_thread().video_channel = channel_index() + 1;
1005                 core::diagnostics::call_context::for_thread().layer = layer_index();
1006
1007                 auto pFP = create_producer(get_dependencies(channel()), parameters());
1008                 
1009                 if(pFP == frame_producer::empty())
1010                         CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(parameters().size() > 0 ? parameters()[0] : L""));
1011
1012                 bool auto_play = contains_param(L"AUTO", parameters());
1013
1014                 auto pFP2 = create_transition_producer(channel()->video_format_desc().field_mode, pFP, transitionInfo);
1015                 if(auto_play)
1016                         channel()->stage().load(layer_index(), pFP2, false, transitionInfo.duration); // TODO: LOOP
1017                 else
1018                         channel()->stage().load(layer_index(), pFP2, false); // TODO: LOOP
1019         
1020                 
1021                 SetReplyString(L"202 LOADBG OK\r\n");
1022
1023                 return true;
1024         }
1025         catch(file_not_found&)
1026         {               
1027                 CASPAR_LOG(error) << L"File not found. No match found for parameters. Check syntax.";
1028                 SetReplyString(L"404 LOADBG ERROR\r\n");
1029                 return false;
1030         }
1031         catch(...)
1032         {
1033                 CASPAR_LOG_CURRENT_EXCEPTION();
1034                 SetReplyString(L"502 LOADBG FAILED\r\n");
1035                 return false;
1036         }
1037 }
1038
1039 bool PauseCommand::DoExecute()
1040 {
1041         try
1042         {
1043                 channel()->stage().pause(layer_index());
1044                 SetReplyString(L"202 PAUSE OK\r\n");
1045                 return true;
1046         }
1047         catch(...)
1048         {
1049                 SetReplyString(L"501 PAUSE FAILED\r\n");
1050         }
1051
1052         return false;
1053 }
1054
1055 bool PlayCommand::DoExecute()
1056 {
1057         try
1058         {
1059                 if(!parameters().empty())
1060                 {
1061                         LoadbgCommand lbg(*this);
1062
1063                         if(!lbg.Execute())
1064                                 throw std::exception();
1065                 }
1066
1067                 channel()->stage().play(layer_index());
1068                 
1069                 SetReplyString(L"202 PLAY OK\r\n");
1070                 return true;
1071         }
1072         catch(...)
1073         {
1074                 SetReplyString(L"501 PLAY FAILED\r\n");
1075         }
1076
1077         return false;
1078 }
1079
1080 bool StopCommand::DoExecute()
1081 {
1082         try
1083         {
1084                 channel()->stage().stop(layer_index());
1085                 SetReplyString(L"202 STOP OK\r\n");
1086                 return true;
1087         }
1088         catch(...)
1089         {
1090                 SetReplyString(L"501 STOP FAILED\r\n");
1091         }
1092
1093         return false;
1094 }
1095
1096 bool ClearCommand::DoExecute()
1097 {
1098         int index = layer_index(std::numeric_limits<int>::min());
1099         if(index != std::numeric_limits<int>::min())
1100                 channel()->stage().clear(index);
1101         else
1102                 channel()->stage().clear();
1103                 
1104         SetReplyString(L"202 CLEAR OK\r\n");
1105
1106         return true;
1107 }
1108
1109 bool PrintCommand::DoExecute()
1110 {
1111         channel()->output().add(create_consumer({ L"IMAGE" }, &channel()->stage()));
1112                 
1113         SetReplyString(L"202 PRINT OK\r\n");
1114
1115         return true;
1116 }
1117
1118 bool LogCommand::DoExecute()
1119 {
1120         if(boost::iequals(parameters().at(0), L"LEVEL"))
1121                 log::set_log_level(parameters().at(1));
1122
1123         SetReplyString(L"202 LOG OK\r\n");
1124
1125         return true;
1126 }
1127
1128 bool CGCommand::DoExecute()
1129 {
1130         try
1131         {
1132                 std::wstring command = boost::to_upper_copy(parameters()[0]);
1133                 if(command == L"ADD")
1134                         return DoExecuteAdd();
1135                 else if(command == L"PLAY")
1136                         return DoExecutePlay();
1137                 else if(command == L"STOP")
1138                         return DoExecuteStop();
1139                 else if(command == L"NEXT")
1140                         return DoExecuteNext();
1141                 else if(command == L"REMOVE")
1142                         return DoExecuteRemove();
1143                 else if(command == L"CLEAR")
1144                         return DoExecuteClear();
1145                 else if(command == L"UPDATE")
1146                         return DoExecuteUpdate();
1147                 else if(command == L"INVOKE")
1148                         return DoExecuteInvoke();
1149                 else if(command == L"INFO")
1150                         return DoExecuteInfo();
1151         }
1152         catch(...)
1153         {
1154                 CASPAR_LOG_CURRENT_EXCEPTION();
1155         }
1156
1157         SetReplyString(L"403 CG ERROR\r\n");
1158         return false;
1159 }
1160
1161 bool CGCommand::ValidateLayer(const std::wstring& layerstring) {
1162         int length = layerstring.length();
1163         for(int i = 0; i < length; ++i) {
1164                 if(!std::isdigit(layerstring[i])) {
1165                         return false;
1166                 }
1167         }
1168
1169         return true;
1170 }
1171
1172 bool CGCommand::DoExecuteAdd() {
1173         //CG 1 ADD 0 "template_folder/templatename" [STARTLABEL] 0/1 [DATA]
1174
1175         int layer = 0;                          //_parameters[1]
1176 //      std::wstring templateName;      //_parameters[2]
1177         std::wstring label;             //_parameters[3]
1178         bool bDoStart = false;          //_parameters[3] alt. _parameters[4]
1179 //      std::wstring data;                      //_parameters[4] alt. _parameters[5]
1180
1181         if(parameters().size() < 4) 
1182         {
1183                 SetReplyString(L"402 CG ERROR\r\n");
1184                 return false;
1185         }
1186         unsigned int dataIndex = 4;
1187
1188         if(!ValidateLayer(parameters()[1])) 
1189         {
1190                 SetReplyString(L"403 CG ERROR\r\n");
1191                 return false;
1192         }
1193
1194         layer = boost::lexical_cast<int>(parameters()[1]);
1195
1196         if(parameters()[3].length() > 1) 
1197         {       //read label
1198                 label = parameters()[3];
1199                 ++dataIndex;
1200
1201                 if(parameters().size() > 4 && parameters()[4].length() > 0)     //read play-on-load-flag
1202                         bDoStart = (parameters()[4][0]==L'1') ? true : false;
1203                 else 
1204                 {
1205                         SetReplyString(L"402 CG ERROR\r\n");
1206                         return false;
1207                 }
1208         }
1209         else if(parameters()[3].length() > 0) { //read play-on-load-flag
1210                 bDoStart = (parameters()[3][0]==L'1') ? true : false;
1211         }
1212         else 
1213         {
1214                 SetReplyString(L"403 CG ERROR\r\n");
1215                 return false;
1216         }
1217
1218         const wchar_t* pDataString = 0;
1219         std::wstring dataFromFile;
1220         if(parameters().size() > dataIndex) 
1221         {       //read data
1222                 const std::wstring& dataString = parameters()[dataIndex];
1223
1224                 if (dataString.at(0) == L'<' || dataString.at(0) == L'{') //the data is XML or Json
1225                         pDataString = dataString.c_str();
1226                 else 
1227                 {
1228                         //The data is not an XML-string, it must be a filename
1229                         std::wstring filename = env::data_folder();
1230                         filename.append(dataString);
1231                         filename.append(L".ftd");
1232
1233                         auto found_file = find_case_insensitive(filename);
1234
1235                         if (found_file)
1236                         {
1237                                 dataFromFile = read_file(boost::filesystem::path(*found_file));
1238                                 pDataString = dataFromFile.c_str();
1239                         }
1240                 }
1241         }
1242
1243         auto filename = parameters()[2];
1244         auto proxy = cg_registry_->get_or_create_proxy(channel(), get_dependencies(channel()), layer_index(core::cg_proxy::DEFAULT_LAYER), filename);
1245
1246         if (proxy == core::cg_proxy::empty())
1247         {
1248                 CASPAR_LOG(warning) << "Could not find template " << parameters()[2];
1249                 SetReplyString(L"404 CG ERROR\r\n");
1250         }
1251         else
1252         {
1253                 proxy->add(layer, filename, bDoStart, label, (pDataString != 0) ? pDataString : L"");
1254         }
1255
1256         return true;
1257 }
1258
1259 bool CGCommand::DoExecutePlay()
1260 {
1261         if(parameters().size() > 1)
1262         {
1263                 if(!ValidateLayer(parameters()[1])) 
1264                 {
1265                         SetReplyString(L"403 CG ERROR\r\n");
1266                         return false;
1267                 }
1268                 int layer = boost::lexical_cast<int>(parameters()[1]);
1269                 cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->play(layer);
1270         }
1271         else
1272         {
1273                 SetReplyString(L"402 CG ERROR\r\n");
1274                 return true;
1275         }
1276
1277         SetReplyString(L"202 CG OK\r\n");
1278         return true;
1279 }
1280
1281 bool CGCommand::DoExecuteStop() 
1282 {
1283         if(parameters().size() > 1)
1284         {
1285                 if(!ValidateLayer(parameters()[1])) 
1286                 {
1287                         SetReplyString(L"403 CG ERROR\r\n");
1288                         return false;
1289                 }
1290                 int layer = boost::lexical_cast<int>(parameters()[1]);
1291                 cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->stop(layer, 0);
1292         }
1293         else 
1294         {
1295                 SetReplyString(L"402 CG ERROR\r\n");
1296                 return true;
1297         }
1298
1299         SetReplyString(L"202 CG OK\r\n");
1300         return true;
1301 }
1302
1303 bool CGCommand::DoExecuteNext()
1304 {
1305         if(parameters().size() > 1) 
1306         {
1307                 if(!ValidateLayer(parameters()[1])) 
1308                 {
1309                         SetReplyString(L"403 CG ERROR\r\n");
1310                         return false;
1311                 }
1312
1313                 int layer = boost::lexical_cast<int>(parameters()[1]);
1314                 cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->next(layer);
1315         }
1316         else 
1317         {
1318                 SetReplyString(L"402 CG ERROR\r\n");
1319                 return true;
1320         }
1321
1322         SetReplyString(L"202 CG OK\r\n");
1323         return true;
1324 }
1325
1326 bool CGCommand::DoExecuteRemove() 
1327 {
1328         if(parameters().size() > 1) 
1329         {
1330                 if(!ValidateLayer(parameters()[1])) 
1331                 {
1332                         SetReplyString(L"403 CG ERROR\r\n");
1333                         return false;
1334                 }
1335
1336                 int layer = boost::lexical_cast<int>(parameters()[1]);
1337                 cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->remove(layer);
1338         }
1339         else 
1340         {
1341                 SetReplyString(L"402 CG ERROR\r\n");
1342                 return true;
1343         }
1344
1345         SetReplyString(L"202 CG OK\r\n");
1346         return true;
1347 }
1348
1349 bool CGCommand::DoExecuteClear() 
1350 {
1351         channel()->stage().clear(layer_index(core::cg_proxy::DEFAULT_LAYER));
1352         SetReplyString(L"202 CG OK\r\n");
1353         return true;
1354 }
1355
1356 bool CGCommand::DoExecuteUpdate() 
1357 {
1358         try
1359         {
1360                 if(!ValidateLayer(parameters().at(1)))
1361                 {
1362                         SetReplyString(L"403 CG ERROR\r\n");
1363                         return false;
1364                 }
1365                                                 
1366                 std::wstring dataString = parameters().at(2);                           
1367                 if (dataString.at(0) != L'<' && dataString.at(0) != L'{')
1368                 {
1369                         //The data is not XML or Json, it must be a filename
1370                         std::wstring filename = env::data_folder();
1371                         filename.append(dataString);
1372                         filename.append(L".ftd");
1373
1374                         dataString = read_file(boost::filesystem::path(filename));
1375                 }               
1376
1377                 int layer = boost::lexical_cast<int>(parameters()[1]);
1378                 cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->update(layer, dataString);
1379         }
1380         catch(...)
1381         {
1382                 SetReplyString(L"402 CG ERROR\r\n");
1383                 return true;
1384         }
1385
1386         SetReplyString(L"202 CG OK\r\n");
1387         return true;
1388 }
1389
1390 bool CGCommand::DoExecuteInvoke() 
1391 {
1392         std::wstringstream replyString;
1393         replyString << L"201 CG OK\r\n";
1394
1395         if(parameters().size() > 2)
1396         {
1397                 if(!ValidateLayer(parameters()[1]))
1398                 {
1399                         SetReplyString(L"403 CG ERROR\r\n");
1400                         return false;
1401                 }
1402                 int layer = boost::lexical_cast<int>(parameters()[1]);
1403                 auto result = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->invoke(layer, parameters()[2]);
1404                 replyString << result << L"\r\n";
1405         }
1406         else 
1407         {
1408                 SetReplyString(L"402 CG ERROR\r\n");
1409                 return true;
1410         }
1411         
1412         SetReplyString(replyString.str());
1413         return true;
1414 }
1415
1416 bool CGCommand::DoExecuteInfo() 
1417 {
1418         std::wstringstream replyString;
1419         replyString << L"201 CG OK\r\n";
1420
1421         if(parameters().size() > 1)
1422         {
1423                 if(!ValidateLayer(parameters()[1]))
1424                 {
1425                         SetReplyString(L"403 CG ERROR\r\n");
1426                         return false;
1427                 }
1428
1429                 int layer = boost::lexical_cast<int>(parameters()[1]);
1430                 auto desc = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->description(layer);
1431                 
1432                 replyString << desc << L"\r\n";
1433         }
1434         else 
1435         {
1436                 auto info = cg_registry_->get_proxy(channel(), layer_index(core::cg_proxy::DEFAULT_LAYER))->template_host_info();
1437                 replyString << info << L"\r\n";
1438         }       
1439
1440         SetReplyString(replyString.str());
1441         return true;
1442 }
1443
1444 bool DataCommand::DoExecute()
1445 {
1446         std::wstring command = boost::to_upper_copy(parameters()[0]);
1447         if(command == L"STORE")
1448                 return DoExecuteStore();
1449         else if(command == L"RETRIEVE")
1450                 return DoExecuteRetrieve();
1451         else if(command == L"REMOVE")
1452                 return DoExecuteRemove();
1453         else if(command == L"LIST")
1454                 return DoExecuteList();
1455
1456         SetReplyString(L"403 DATA ERROR\r\n");
1457         return false;
1458 }
1459
1460 bool DataCommand::DoExecuteStore() 
1461 {
1462         if(parameters().size() < 3) 
1463         {
1464                 SetReplyString(L"402 DATA STORE ERROR\r\n");
1465                 return false;
1466         }
1467
1468         std::wstring filename = env::data_folder();
1469         filename.append(parameters()[1]);
1470         filename.append(L".ftd");
1471
1472         auto data_path = boost::filesystem::path(filename).parent_path().wstring();
1473         auto found_data_path = find_case_insensitive(data_path);
1474
1475         if (found_data_path)
1476                 data_path = *found_data_path;
1477
1478         if(!boost::filesystem::exists(data_path))
1479                 boost::filesystem::create_directories(data_path);
1480
1481         auto found_filename = find_case_insensitive(filename);
1482
1483         if (found_filename)
1484                 filename = *found_filename; // Overwrite case insensitive.
1485
1486         boost::filesystem::wofstream datafile(filename);
1487         if(!datafile) 
1488         {
1489                 SetReplyString(L"501 DATA STORE FAILED\r\n");
1490                 return false;
1491         }
1492
1493         datafile << static_cast<wchar_t>(65279); // UTF-8 BOM character
1494         datafile << parameters()[2] << std::flush;
1495         datafile.close();
1496
1497         std::wstring replyString = L"202 DATA STORE OK\r\n";
1498         SetReplyString(replyString);
1499         return true;
1500 }
1501
1502 bool DataCommand::DoExecuteRetrieve() 
1503 {
1504         if(parameters().size() < 2) 
1505         {
1506                 SetReplyString(L"402 DATA RETRIEVE ERROR\r\n");
1507                 return false;
1508         }
1509
1510         std::wstring filename = env::data_folder();
1511         filename.append(parameters()[1]);
1512         filename.append(L".ftd");
1513
1514         std::wstring file_contents;
1515
1516         auto found_file = find_case_insensitive(filename);
1517
1518         if (found_file)
1519                 file_contents = read_file(boost::filesystem::path(*found_file));
1520
1521         if (file_contents.empty()) 
1522         {
1523                 SetReplyString(L"404 DATA RETRIEVE ERROR\r\n");
1524                 return false;
1525         }
1526
1527         std::wstringstream reply(L"201 DATA RETRIEVE OK\r\n");
1528
1529         std::wstringstream file_contents_stream(file_contents);
1530         std::wstring line;
1531         
1532         bool firstLine = true;
1533         while(std::getline(file_contents_stream, line))
1534         {
1535                 if(firstLine)
1536                         firstLine = false;
1537                 else
1538                         reply << "\n";
1539
1540                 reply << line;
1541         }
1542
1543         reply << "\r\n";
1544         SetReplyString(reply.str());
1545         return true;
1546 }
1547
1548 bool DataCommand::DoExecuteRemove()
1549
1550         if (parameters().size() < 2)
1551         {
1552                 SetReplyString(L"402 DATA REMOVE ERROR\r\n");
1553                 return false;
1554         }
1555
1556         std::wstring filename = env::data_folder();
1557         filename.append(parameters()[1]);
1558         filename.append(L".ftd");
1559
1560         if (!boost::filesystem::exists(filename))
1561         {
1562                 SetReplyString(L"404 DATA REMOVE ERROR\r\n");
1563                 return false;
1564         }
1565
1566         if (!boost::filesystem::remove(filename))
1567         {
1568                 SetReplyString(L"403 DATA REMOVE ERROR\r\n");
1569                 return false;
1570         }
1571
1572         SetReplyString(L"201 DATA REMOVE OK\r\n");
1573
1574         return true;
1575 }
1576
1577 bool DataCommand::DoExecuteList() 
1578 {
1579         std::wstringstream replyString;
1580         replyString << L"200 DATA LIST OK\r\n";
1581
1582         for (boost::filesystem::recursive_directory_iterator itr(env::data_folder()), end; itr != end; ++itr)
1583         {                       
1584                 if(boost::filesystem::is_regular_file(itr->path()))
1585                 {
1586                         if(!boost::iequals(itr->path().extension().wstring(), L".ftd"))
1587                                 continue;
1588                         
1589                         auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::data_folder().size()-1, itr->path().wstring().size()));
1590                         
1591                         auto str = relativePath.replace_extension(L"").generic_wstring();
1592                         if(str[0] == L'\\' || str[0] == L'/')
1593                                 str = std::wstring(str.begin() + 1, str.end());
1594
1595                         replyString << str << L"\r\n";
1596                 }
1597         }
1598         
1599         replyString << L"\r\n";
1600
1601         SetReplyString(boost::to_upper_copy(replyString.str()));
1602         return true;
1603 }
1604
1605 bool CinfCommand::DoExecute()
1606 {
1607         std::wstringstream replyString;
1608         
1609         try
1610         {
1611                 std::wstring info;
1612                 for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end && info.empty(); ++itr)
1613                 {
1614                         auto path = itr->path();
1615                         auto file = path.replace_extension(L"").filename().wstring();
1616                         if(boost::iequals(file, parameters().at(0)))
1617                                 info += MediaInfo(itr->path(), system_info_repo_) + L"\r\n";
1618                 }
1619
1620                 if(info.empty())
1621                 {
1622                         SetReplyString(L"404 CINF ERROR\r\n");
1623                         return false;
1624                 }
1625                 replyString << L"200 CINF OK\r\n";
1626                 replyString << info << "\r\n";
1627         }
1628         catch(...)
1629         {
1630                 CASPAR_LOG_CURRENT_EXCEPTION();
1631                 SetReplyString(L"404 CINF ERROR\r\n");
1632                 return false;
1633         }
1634         
1635         SetReplyString(replyString.str());
1636         return true;
1637 }
1638
1639 void GenerateChannelInfo(int index, const spl::shared_ptr<core::video_channel>& pChannel, std::wstringstream& replyString)
1640 {
1641         replyString << index+1 << L" " << pChannel->video_format_desc().name << L" PLAYING\r\n";
1642 }
1643
1644 bool InfoCommand::DoExecute()
1645 {
1646         std::wstringstream replyString;
1647         
1648         boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
1649
1650         try
1651         {
1652                 if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"TEMPLATE"))
1653                 {               
1654                         replyString << L"201 INFO TEMPLATE OK\r\n";
1655
1656                         auto filename = parameters().at(1);
1657                                                 
1658                         std::wstringstream str;
1659                         str << u16(cg_registry_->read_meta_info(filename));
1660                         boost::property_tree::wptree info;
1661                         boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
1662
1663                         boost::property_tree::xml_parser::write_xml(replyString, info, w);
1664                 }
1665                 else if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"CONFIG"))
1666                 {               
1667                         replyString << L"201 INFO CONFIG OK\r\n";
1668
1669                         boost::property_tree::write_xml(replyString, caspar::env::properties(), w);
1670                 }
1671                 else if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"PATHS"))
1672                 {
1673                         replyString << L"201 INFO PATHS OK\r\n";
1674
1675                         boost::property_tree::wptree info;
1676                         info.add_child(L"paths", caspar::env::properties().get_child(L"configuration.paths"));
1677                         info.add(L"paths.initial-path", boost::filesystem::initial_path().wstring() + L"/");
1678
1679                         boost::property_tree::write_xml(replyString, info, w);
1680                 }
1681                 else if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"SYSTEM"))
1682                 {
1683                         replyString << L"201 INFO SYSTEM OK\r\n";
1684                         
1685                         boost::property_tree::wptree info;
1686                         
1687                         info.add(L"system.name",                                        caspar::system_product_name());
1688                         info.add(L"system.os.description",                      caspar::os_description());
1689                         info.add(L"system.cpu",                                         caspar::cpu_info());
1690
1691                         system_info_repo_->fill_information(info);
1692                                                 
1693                         boost::property_tree::write_xml(replyString, info, w);
1694                 }
1695                 else if(parameters().size() >= 1 && boost::iequals(parameters()[0], L"SERVER"))
1696                 {
1697                         replyString << L"201 INFO SERVER OK\r\n";
1698                         
1699                         boost::property_tree::wptree info;
1700
1701                         int index = 0;
1702                         for (auto& channel : channels())
1703                                 info.add_child(L"channels.channel", channel.channel->info())
1704                                         .add(L"index", ++index);
1705                         
1706                         boost::property_tree::write_xml(replyString, info, w);
1707                 }
1708                 else // channel
1709                 {                       
1710                         if(parameters().size() >= 1)
1711                         {
1712                                 replyString << L"201 INFO OK\r\n";
1713                                 boost::property_tree::wptree info;
1714
1715                                 std::vector<std::wstring> split;
1716                                 boost::split(split, parameters()[0], boost::is_any_of("-"));
1717                                         
1718                                 int layer = std::numeric_limits<int>::min();
1719                                 int channel = boost::lexical_cast<int>(split[0]) - 1;
1720
1721                                 if(split.size() > 1)
1722                                         layer = boost::lexical_cast<int>(split[1]);
1723                                 
1724                                 if(layer == std::numeric_limits<int>::min())
1725                                 {       
1726                                         info.add_child(L"channel", channels().at(channel).channel->info())
1727                                                         .add(L"index", channel);
1728                                 }
1729                                 else
1730                                 {
1731                                         if(parameters().size() >= 2)
1732                                         {
1733                                                 if(boost::iequals(parameters()[1], L"B"))
1734                                                         info.add_child(L"producer", channels().at(channel).channel->stage().background(layer).get()->info());
1735                                                 else
1736                                                         info.add_child(L"producer", channels().at(channel).channel->stage().foreground(layer).get()->info());
1737                                         }
1738                                         else
1739                                         {
1740                                                 info.add_child(L"layer", channels().at(channel).channel->stage().info(layer).get())
1741                                                         .add(L"index", layer);
1742                                         }
1743                                 }
1744                                 boost::property_tree::xml_parser::write_xml(replyString, info, w);
1745                         }
1746                         else
1747                         {
1748                                 // This is needed for backwards compatibility with old clients
1749                                 replyString << L"200 INFO OK\r\n";
1750                                 for(size_t n = 0; n < channels().size(); ++n)
1751                                         GenerateChannelInfo(n, channels()[n].channel, replyString);
1752                         }
1753
1754                 }
1755         }
1756         catch(...)
1757         {
1758                 CASPAR_LOG_CURRENT_EXCEPTION();
1759                 SetReplyString(L"403 INFO ERROR\r\n");
1760                 return false;
1761         }
1762
1763         replyString << L"\r\n";
1764         SetReplyString(replyString.str());
1765         return true;
1766 }
1767
1768 bool ClsCommand::DoExecute()
1769 {
1770         try
1771         {
1772                 std::wstringstream replyString;
1773                 replyString << L"200 CLS OK\r\n";
1774                 replyString << ListMedia(system_info_repo_);
1775                 replyString << L"\r\n";
1776                 SetReplyString(boost::to_upper_copy(replyString.str()));
1777         }
1778         catch(...)
1779         {
1780                 CASPAR_LOG_CURRENT_EXCEPTION();
1781                 SetReplyString(L"501 CLS FAILED\r\n");
1782                 return false;
1783         }
1784
1785         return true;
1786 }
1787
1788 bool TlsCommand::DoExecute()
1789 {
1790         try
1791         {
1792                 std::wstringstream replyString;
1793                 replyString << L"200 TLS OK\r\n";
1794
1795                 replyString << ListTemplates(cg_registry_);
1796                 replyString << L"\r\n";
1797
1798                 SetReplyString(replyString.str());
1799         }
1800         catch(...)
1801         {
1802                 CASPAR_LOG_CURRENT_EXCEPTION();
1803                 SetReplyString(L"501 TLS FAILED\r\n");
1804                 return false;
1805         }
1806         return true;
1807 }
1808
1809 bool VersionCommand::DoExecute()
1810 {
1811         std::wstring replyString = L"201 VERSION OK\r\n" + env::version() + L"\r\n";
1812
1813         if (parameters().size() > 0 && !boost::iequals(parameters()[0], L"SERVER"))
1814         {
1815                 auto version = system_info_repo_->get_version(parameters().at(0));
1816
1817                 if (version.empty())
1818                         replyString = L"403 VERSION ERROR\r\n";
1819                 else
1820                         replyString = L"201 VERSION OK\r\n" + version + L"\r\n";
1821         }
1822
1823         SetReplyString(replyString);
1824         return true;
1825 }
1826
1827 bool ByeCommand::DoExecute()
1828 {
1829         client()->disconnect();
1830         return true;
1831 }
1832
1833 bool SetCommand::DoExecute()
1834 {
1835         try
1836         {
1837                 std::wstring name = boost::to_upper_copy(parameters()[0]);
1838                 std::wstring value = boost::to_upper_copy(parameters()[1]);
1839
1840                 if(name == L"MODE")
1841                 {
1842                         auto format_desc = core::video_format_desc(value);
1843                         if(format_desc.format != core::video_format::invalid)
1844                         {
1845                                 channel()->video_format_desc(format_desc);
1846                                 SetReplyString(L"202 SET MODE OK\r\n");
1847                         }
1848                         else
1849                                 SetReplyString(L"501 SET MODE FAILED\r\n");
1850                 }
1851                 else
1852                 {
1853                         this->SetReplyString(L"403 SET ERROR\r\n");
1854                 }
1855         }
1856         catch(...)
1857         {
1858                 CASPAR_LOG_CURRENT_EXCEPTION();
1859                 SetReplyString(L"501 SET FAILED\r\n");
1860                 return false;
1861         }
1862
1863         return true;
1864 }
1865
1866 bool LockCommand::DoExecute()
1867 {
1868         try
1869         {
1870                 auto it = parameters().begin();
1871
1872                 std::shared_ptr<caspar::IO::lock_container> lock;
1873                 try
1874                 {
1875                         int channel_index = boost::lexical_cast<int>(*it) - 1;
1876                         lock = channels().at(channel_index).lock;
1877                 }
1878                 catch(const boost::bad_lexical_cast&) {}
1879                 catch(...)
1880                 {
1881                         SetReplyString(L"401 LOCK ERROR\r\n");
1882                         return false;
1883                 }
1884
1885                 if(lock)
1886                         ++it;
1887
1888                 if(it == parameters().end())    //too few parameters
1889                 {
1890                         SetReplyString(L"402 LOCK ERROR\r\n");
1891                         return false;
1892                 }
1893
1894                 std::wstring command = boost::to_upper_copy(*it);
1895                 if(command == L"ACQUIRE")
1896                 {
1897                         ++it;
1898                         if(it == parameters().end())    //too few parameters
1899                         {
1900                                 SetReplyString(L"402 LOCK ACQUIRE ERROR\r\n");
1901                                 return false;
1902                         }
1903                         std::wstring lock_phrase = (*it);
1904
1905                         //TODO: read options
1906
1907                         if(lock)
1908                         {
1909                                 //just lock one channel
1910                                 if(!lock->try_lock(lock_phrase, client()))
1911                                 {
1912                                         SetReplyString(L"503 LOCK ACQUIRE FAILED\r\n");
1913                                         return false;
1914                                 }
1915                         }
1916                         else
1917                         {
1918                                 //TODO: lock all channels
1919                                 CASPAR_THROW_EXCEPTION(not_implemented());
1920                         }
1921                         SetReplyString(L"202 LOCK ACQUIRE OK\r\n");
1922
1923                 }
1924                 else if(command == L"RELEASE")
1925                 {
1926                         if(lock)
1927                         {
1928                                 lock->release_lock(client());
1929                         }
1930                         else
1931                         {
1932                                 //TODO: release all channels
1933                                 CASPAR_THROW_EXCEPTION(not_implemented());
1934                         }
1935                         SetReplyString(L"202 LOCK RELEASE OK\r\n");
1936                 }
1937                 else if(command == L"CLEAR")
1938                 {
1939                         std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
1940                         std::wstring client_override_phrase;
1941                         if(!override_phrase.empty())
1942                         {
1943                                 ++it;
1944                                 if(it == parameters().end())
1945                                 {
1946                                         SetReplyString(L"402 LOCK CLEAR ERROR\r\n");
1947                                         return false;
1948                                 }
1949                                 client_override_phrase = (*it);
1950                         }
1951
1952                         if(lock)
1953                         {
1954                                 //just clear one channel
1955                                 if(client_override_phrase != override_phrase)
1956                                 {
1957                                         SetReplyString(L"503 LOCK CLEAR FAILED\r\n");
1958                                         return false;
1959                                 }
1960                                 
1961                                 lock->clear_locks();
1962                         }
1963                         else
1964                         {
1965                                 //TODO: clear all channels
1966                                 CASPAR_THROW_EXCEPTION(not_implemented());
1967                         }
1968
1969                         SetReplyString(L"202 LOCK CLEAR OK\r\n");
1970                 }
1971                 else
1972                 {
1973                         SetReplyString(L"403 LOCK ERROR\r\n");
1974                         return false;
1975                 }
1976         }
1977         catch(not_implemented&)
1978         {
1979                 SetReplyString(L"600 LOCK FAILED\r\n");
1980                 return false;
1981         }
1982         catch(...)
1983         {
1984                 CASPAR_LOG_CURRENT_EXCEPTION();
1985                 SetReplyString(L"501 LOCK FAILED\r\n");
1986                 return false;
1987         }
1988
1989         return true;
1990 }
1991
1992 bool ThumbnailCommand::DoExecute()
1993 {
1994         std::wstring command = boost::to_upper_copy(parameters()[0]);
1995
1996         if (command == L"RETRIEVE")
1997                 return DoExecuteRetrieve();
1998         else if (command == L"LIST")
1999                 return DoExecuteList();
2000         else if (command == L"GENERATE")
2001                 return DoExecuteGenerate();
2002         else if (command == L"GENERATE_ALL")
2003                 return DoExecuteGenerateAll();
2004
2005         SetReplyString(L"403 THUMBNAIL ERROR\r\n");
2006         return false;
2007 }
2008
2009 bool ThumbnailCommand::DoExecuteRetrieve() 
2010 {
2011         if(parameters().size() < 2) 
2012         {
2013                 SetReplyString(L"402 THUMBNAIL RETRIEVE ERROR\r\n");
2014                 return false;
2015         }
2016
2017         std::wstring filename = env::thumbnails_folder();
2018         filename.append(parameters()[1]);
2019         filename.append(L".png");
2020
2021         std::wstring file_contents;
2022
2023         auto found_file = find_case_insensitive(filename);
2024
2025         if (found_file)
2026                 file_contents = read_file_base64(boost::filesystem::path(*found_file));
2027
2028         if (file_contents.empty())
2029         {
2030                 SetReplyString(L"404 THUMBNAIL RETRIEVE ERROR\r\n");
2031                 return false;
2032         }
2033
2034         std::wstringstream reply;
2035
2036         reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
2037         reply << file_contents;
2038         reply << L"\r\n";
2039         SetReplyString(reply.str());
2040         return true;
2041 }
2042
2043 bool ThumbnailCommand::DoExecuteList()
2044 {
2045         std::wstringstream replyString;
2046         replyString << L"200 THUMBNAIL LIST OK\r\n";
2047
2048         for (boost::filesystem::recursive_directory_iterator itr(env::thumbnails_folder()), end; itr != end; ++itr)
2049         {      
2050                 if(boost::filesystem::is_regular_file(itr->path()))
2051                 {
2052                         if(!boost::iequals(itr->path().extension().wstring(), L".png"))
2053                                 continue;
2054
2055                         auto relativePath = boost::filesystem::path(itr->path().wstring().substr(env::thumbnails_folder().size()-1, itr->path().wstring().size()));
2056
2057                         auto str = relativePath.replace_extension(L"").generic_wstring();
2058                         if(str[0] == '\\' || str[0] == '/')
2059                                 str = std::wstring(str.begin() + 1, str.end());
2060
2061                         auto mtime = boost::filesystem::last_write_time(itr->path());
2062                         auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
2063                         auto file_size = boost::filesystem::file_size(itr->path());
2064
2065                         replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
2066                 }
2067         }
2068
2069         replyString << L"\r\n";
2070
2071         SetReplyString(boost::to_upper_copy(replyString.str()));
2072         return true;
2073 }
2074
2075 bool ThumbnailCommand::DoExecuteGenerate()
2076 {
2077         if (parameters().size() < 2) 
2078         {
2079                 SetReplyString(L"402 THUMBNAIL GENERATE ERROR\r\n");
2080                 return false;
2081         }
2082
2083         if (thumb_gen_)
2084         {
2085                 thumb_gen_->generate(parameters()[1]);
2086                 SetReplyString(L"202 THUMBNAIL GENERATE OK\r\n");
2087                 return true;
2088         }
2089         else
2090         {
2091                 SetReplyString(L"500 THUMBNAIL GENERATE ERROR\r\n");
2092                 return false;
2093         }
2094 }
2095
2096 bool ThumbnailCommand::DoExecuteGenerateAll()
2097 {
2098         if (thumb_gen_)
2099         {
2100                 thumb_gen_->generate_all();
2101                 SetReplyString(L"202 THUMBNAIL GENERATE_ALL OK\r\n");
2102                 return true;
2103         }
2104         else
2105         {
2106                 SetReplyString(L"500 THUMBNAIL GENERATE_ALL ERROR\r\n");
2107                 return false;
2108         }
2109 }
2110
2111 bool KillCommand::DoExecute()
2112 {
2113         shutdown_server_now_->set_value(false); //false for not attempting to restart
2114         SetReplyString(L"202 KILL OK\r\n");
2115         return true;
2116 }
2117
2118 bool RestartCommand::DoExecute()
2119 {
2120         shutdown_server_now_->set_value(true);  //true for attempting to restart
2121         SetReplyString(L"202 RESTART OK\r\n");
2122         return true;
2123 }
2124
2125 }       //namespace amcp
2126 }}      //namespace caspar