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